Skip to content

Commit

Permalink
add gaf support
Browse files Browse the repository at this point in the history
  • Loading branch information
damdo committed Sep 7, 2023
1 parent 6a46c50 commit bcb9684
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 7 deletions.
86 changes: 83 additions & 3 deletions cmd/gom/cmd/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"time"

"github.com/damdo/gokrazy-machine/internal/disk"
"github.com/damdo/gokrazy-machine/internal/gaf"
"github.com/damdo/gokrazy-machine/internal/oci"
"github.com/damdo/gokrazy-machine/internal/ports"
"github.com/damdo/gokrazy-machine/internal/qemu"
Expand All @@ -39,6 +40,7 @@ type playImplConfig struct {
netNat string
netShared string
oci string
gaf string
boot string
root string
mbr string
Expand All @@ -50,14 +52,15 @@ type playImplConfig struct {
}

const arm64, amd64 = "arm64", "amd64"
const modeOCI, modeFull, modeParts = "oci", "full", "parts"
const modeOCI, modeFull, modeParts, modeGaf = "oci", "full", "parts", "gaf"

Check failure on line 55 in cmd/gom/cmd/play.go

View workflow job for this annotation

GitHub Actions / lint

const `modeGaf` is unused (unused)

var playImpl playImplConfig
var errUnsupportedArch = errors.New("error unsupported architecture")

func init() {
playCmd.Flags().StringVar(&playImpl.arch, "arch", amd64, "arch")
playCmd.Flags().StringVar(&playImpl.full, "full", "", "path to the img of the drive file")
playCmd.Flags().StringVar(&playImpl.gaf, "gaf", "", "path to the .gaf (gokrazy archive format) of the drive file")
playCmd.Flags().StringVar(&playImpl.oci, "oci", "", "path to the remote oci artifact reference "+
"(e.g. docker.io/damdo/gokrazy:sample-amd64)")
playCmd.Flags().StringVar(&playImpl.boot, "boot", "", "path to the boot part of the drive")
Expand Down Expand Up @@ -89,9 +92,10 @@ func (r *playImplConfig) play(ctx context.Context, args []string, stdout, stderr
mbrSource := "mbr.img"
bootSource := "boot.img"
rootSource := "root.img"
sbomSource := "sbom.json"
destPath := "disk.img"

diskFile, _, err := obtainDiskFile(ctx, baseDir, mbrSource, bootSource, rootSource, destPath)
diskFile, _, err := obtainDiskFile(ctx, baseDir, mbrSource, bootSource, rootSource, sbomSource, destPath)
if err != nil {
log.Fatalln(fmt.Errorf("error obtaining disk file: %w", err))
}
Expand Down Expand Up @@ -185,12 +189,13 @@ func fmtQemuConfig(cfg []string) string {
}

func obtainDiskFile(ctx context.Context, baseDir, mbrSourceName,

Check failure on line 191 in cmd/gom/cmd/play.go

View workflow job for this annotation

GitHub Actions / lint

cognitive complexity 32 of func `obtainDiskFile` is high (> 30) (gocognit)
bootSourceName, rootSourceName, destName string) (string, string, error) {
bootSourceName, rootSourceName, sbomSourceName, destName string) (string, string, error) {
var diskFile, mode string

mbrSourcePath := path.Join(baseDir, mbrSourceName)
bootSourcePath := path.Join(baseDir, bootSourceName)
rootSourcePath := path.Join(baseDir, rootSourceName)
// sbomSourcePath := path.Join(baseDir, sbomSourceName)
destPath := path.Join(baseDir, destName)

switch {
Expand All @@ -213,6 +218,81 @@ func obtainDiskFile(ctx context.Context, baseDir, mbrSourceName,
diskFile = destPath
mode = modeOCI

case playImpl.gaf != "":
log.Println("starting in gaf mode")

// Extract multi part images from gaf.

f, err := os.Open(path.Clean(playImpl.gaf))
if err != nil {
log.Fatalln(fmt.Errorf("unable to open gaf file: %w", err))
}
fi, err := f.Stat()
if err != nil {
log.Fatalln(fmt.Errorf("unable to stat gaf file: %w", err))
}

gafReadClosers, err := gaf.Extract(ctx, f, fi.Size())
if err != nil {
f.Close()
log.Fatalln(fmt.Errorf("unable to extract gaf file content: %w", err))
}

// MBR.
mbrFile, err := os.Create(mbrSourcePath)
if err != nil {
mbrFile.Close()
log.Fatalln(fmt.Errorf("unable to create temporary MBR file: %w", err))
}

if _, err := io.Copy(mbrFile, gafReadClosers.MBRRC); err != nil {
mbrFile.Close()
log.Fatalln(fmt.Errorf("unable to write temporary MBR file: %w", err))
}
mbrFile.Close()
gafReadClosers.MBRRC.Close()

// Boot.
bootFile, err := os.Create(bootSourcePath)
if err != nil {
bootFile.Close()
log.Fatalln(fmt.Errorf("unable to create temporary boot file: %w", err))
}

if _, err := io.Copy(bootFile, gafReadClosers.BootRC); err != nil {
bootFile.Close()
log.Fatalln(fmt.Errorf("unable to write temporary boot file: %w", err))
}
bootFile.Close()
gafReadClosers.BootRC.Close()

// Root.
rootFile, err := os.Create(rootSourcePath)
if err != nil {
rootFile.Close()
log.Fatalln(fmt.Errorf("unable to create temporary root file: %w", err))
}

if _, err := io.Copy(rootFile, gafReadClosers.RootRC); err != nil {
rootFile.Close()
log.Fatalln(fmt.Errorf("unable to write temporary root file: %w", err))
}
rootFile.Close()
gafReadClosers.RootRC.Close()

log.Printf("merging oci artifact files (disk part images: %s, %s, %s) to a single %s image",
mbrSourcePath, bootSourcePath, rootSourcePath, destPath)

// Create a full disk img starting from disk pieces (mbr, boot, root).
if err := disk.PartsToFull(mbrSourcePath, bootSourcePath, rootSourcePath, destPath); err != nil {
log.Fatalln(fmt.Errorf("unable to create full disk img from oci artifact files: %w", err))
}

diskFile = destPath
mode = modeOCI

f.Close()

case playImpl.boot != "" && playImpl.root != "" && playImpl.mbr != "":
log.Println("starting in multi part disk mode")

Expand Down
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,19 @@ require (
)

require (
github.com/CalebQ42/fuse v0.1.0 // indirect
github.com/CalebQ42/squashfs v0.8.4 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pierrec/lz4/v4 v4.1.18 // indirect
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e // indirect
github.com/seaweedfs/fuse v1.2.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
github.com/CalebQ42/fuse v0.1.0 h1:KLCNjun7zcd2kBNVFfH+SWJyhuwJdE0nhw5/q8K8HGQ=
github.com/CalebQ42/fuse v0.1.0/go.mod h1:pJpoKG03HJKVhsp8o0YQYqmfbFsr3Eowt90yQGQVO+4=
github.com/CalebQ42/squashfs v0.8.4 h1:HnthgRKuLliiMwYsPTSE/ln2zECt7UelYcbsUc5p+PA=
github.com/CalebQ42/squashfs v0.8.4/go.mod h1:CmGHRknB7BlYJ49qSTGpW8wnFcGFdZW0l6+qHOvFr5c=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/gokrazy/tools v0.0.0-20221120152115-b0f51bdf9220 h1:wiyBaCnQSyaAY6KQJ+yr9Dm7q948skXh5ZtwUc1AJ6Q=
github.com/gokrazy/tools v0.0.0-20221120152115-b0f51bdf9220/go.mod h1:o5QgDgPz+z/yecPXDyyJniUo8JurN7kdghCeINtTBOI=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/seaweedfs/fuse v1.2.2 h1:01l8OjIdyATRNqVc/gDPgFobuC8ubQF3hRKOPColROw=
github.com/seaweedfs/fuse v1.2.2/go.mod h1:iwbDQv5BZACY54r6AO/6xsLNuMaYcBKSkLTZVfmK594=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 h1:rw6UNGRMfarCepjI8qOepea/SXwIBVfTKjztZ5gBbq4=
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
29 changes: 26 additions & 3 deletions internal/disk/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"os"

"github.com/CalebQ42/squashfs"
"github.com/gokrazy/tools/packer"
)

Expand All @@ -23,11 +24,14 @@ const (

// PartsToFull merges multi parts (mbr, boot, root) image files of a disk into a single disk image file.
func PartsToFull(mbrSourcePath, bootSourcePath, rootSourcePath, destPath string) error {
// TODO: read this from the source imgs if possible
// TODO: make this a mandatory flag for parts and gaf.
var targetStorageBytes = 2147483648

// TODO: read hostname from source root img
p := packer.NewPackForHost("gokrazy")
hostname, err := getHostname(rootSourcePath)
if err != nil {
return fmt.Errorf("error getting hostname from root file: %w", err)
}
p := packer.NewPackForHost(hostname)

// TODO: read these from the source imgs if possible
p.UsePartuuid = true
Expand Down Expand Up @@ -103,3 +107,22 @@ func PartsToFull(mbrSourcePath, bootSourcePath, rootSourcePath, destPath string)

return nil
}

func getHostname(rootSourcePath string) (string, error) {
f, err := os.Open(rootSourcePath)
if err != nil {
return "", fmt.Errorf("error opening root file: %w", err)
}
rd, err := squashfs.NewReader(f)
if err != nil {
return "", fmt.Errorf("error reading root file squashFS: %w", err)
}

hostnamePath := "etc/hostname"
b, err := rd.ReadFile(hostnamePath)
if err != nil {
return "", fmt.Errorf("error opening root squashFS hostname file %q: %w", hostnamePath, err)
}

return string(b), nil
}
81 changes: 81 additions & 0 deletions internal/gaf/gaf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package gaf

import (
"archive/zip"
"context"
"errors"
"fmt"
"io"
)

// ErrMaformedGaf denotes the error for failed extraction of a gaf
// due to it being malformed.
var ErrMaformedGaf = errors.New("unable to extract malformed gaf")

// ReadClosers holds ReadClosers for the content of the gaf file.
type ReadClosers struct {
MBRRC io.ReadCloser
BootRC io.ReadCloser
RootRC io.ReadCloser
SBOMRC io.ReadCloser
}

const (
MBR string = "mbr.img"
Boot string = "boot.img"
Root string = "root.img"
SBOM string = "sbom.json"
)

// Extract extracts the content of a gaf archive.
func Extract(ctx context.Context, source io.ReaderAt, size int64) (ReadClosers, error) {
gafRCs := ReadClosers{}

reader, err := zip.NewReader(source, size)
if err != nil {
return gafRCs, fmt.Errorf("error reading from zip reader: %w", err)
}

// Check the gaf archive contains the files described in the gaf spec.
for _, file := range reader.File {
switch file.Name {
case MBR:
reader, err := file.Open()
if err != nil {
return ReadClosers{}, err
}
gafRCs.MBRRC = reader

case Boot:
reader, err := file.Open()
if err != nil {
return ReadClosers{}, err
}
gafRCs.BootRC = reader

case Root:
reader, err := file.Open()
if err != nil {
return ReadClosers{}, err
}
gafRCs.RootRC = reader

case SBOM:
reader, err := file.Open()
if err != nil {
return ReadClosers{}, err
}
gafRCs.SBOMRC = reader
}
}

if gafRCs.MBRRC == nil ||
gafRCs.BootRC == nil ||
gafRCs.RootRC == nil ||
gafRCs.SBOMRC == nil {
return ReadClosers{}, ErrMaformedGaf
}

// Unzip archive to readers.
return gafRCs, nil
}

0 comments on commit bcb9684

Please sign in to comment.