From f4271ff2f0cbb11b1625b0c4966f321e734381ae Mon Sep 17 00:00:00 2001 From: Charalampos Mainas Date: Fri, 10 Jan 2025 18:28:09 +0000 Subject: [PATCH] Add support for bunnyfile Add support to read and create the unikernel OCI image for urunc, reading a yaml file that has the structure of bunnyfile. Signed-off-by: Charalampos Mainas --- go.mod | 3 ++ go.sum | 9 ++++ hops/package.go | 115 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) diff --git a/go.mod b/go.mod index 4266a2a..7305785 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module bunny go 1.23.4 require ( + github.com/hashicorp/go-version v1.7.0 github.com/moby/buildkit v0.18.2 github.com/opencontainers/image-spec v1.1.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -29,6 +31,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/in-toto/in-toto-golang v0.5.0 // indirect github.com/klauspost/compress v1.17.11 // indirect + github.com/kr/text v0.2.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/sys/signal v0.7.1 // indirect diff --git a/go.sum b/go.sum index 6729e5b..355315d 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,7 @@ github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oL github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -63,12 +64,18 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/in-toto/in-toto-golang v0.5.0 h1:hb8bgwr0M2hGdDsLjkJ3ZqJ8JFLL/tgYdAxF/XEFBbY= github.com/in-toto/in-toto-golang v0.5.0/go.mod h1:/Rq0IZHLV7Ku5gielPT4wPHJfH1GdHMCq8+WPxw8/BE= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/moby/buildkit v0.18.2 h1:l86uBvxh4ntNoUUg3Y0eGTbKg1PbUh6tawJ4Xt75SpQ= github.com/moby/buildkit v0.18.2/go.mod h1:vCR5CX8NGsPTthTg681+9kdmfvkvqJBXEv71GZe5msU= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -172,6 +179,8 @@ google.golang.org/grpc v1.66.3/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hops/package.go b/hops/package.go index ba03c5d..5d8e365 100644 --- a/hops/package.go +++ b/hops/package.go @@ -22,24 +22,139 @@ import ( "bytes" "strings" + "gopkg.in/yaml.v3" "github.com/moby/buildkit/client/llb" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/moby/buildkit/frontend/dockerfile/parser" + "github.com/hashicorp/go-version" ) const ( + DefaultKernelPath string = "/.boot/kernel" + DefaultInitrdPath string = "/.boot/initrd" unikraftKernelPath string = "/unikraft/bin/kernel" unikraftHub string = "unikraft.org" uruncJSONPath string = "/urunc.json" + bunnyFileVersion string = "0.1" ) +type HopsPlatform struct { + Framework string `yaml:"framework"` + Version string `yaml:"version"` + Monitor string `yaml:"monitor"` + Arch string `yaml:"arch"` +} + +type HopsRootfs struct { + From string `yaml:"from"` + Path string `yaml:"path"` +} + +type HopsKernel struct { + From string `yaml:"from"` + Path string `yaml:"path"` +} + +type Hops struct { + Version string `yaml:"version"` + Platform HopsPlatform `yaml:"platforms"` + Rootfs HopsRootfs `yaml:"rootfs"` + Kernel HopsKernel `yaml:"kernel"` + Cmd string `yaml:"cmdline"` +} + type PackInstructions struct { Base string // The Base image to use Copies map[string]string // Mappings of files top copy, source as key and destination as value Annots map[string]string // Annotations } +// HopsToPack converts Hops into PackInstructions +func HopsToPack(hops Hops) (*PackInstructions, error) { + var instr *PackInstructions + instr = new(PackInstructions) + instr.Copies = make(map[string]string) + instr.Annots = make(map[string]string) + + if hops.Kernel.From == "local" { + instr.Base = "scratch" + instr.Copies[hops.Kernel.Path] = DefaultKernelPath + instr.Annots["com.urunc.unikernel.binary"] = DefaultKernelPath + } else { + instr.Base = hops.Kernel.From + instr.Annots["com.urunc.unikernel.binary"] = hops.Kernel.Path + } + + if hops.Rootfs.From == "local" && hops.Platform.Framework == "unikraft" { + instr.Copies[hops.Rootfs.Path] = DefaultInitrdPath + instr.Annots["com.urunc.unikernel.initrd"] = DefaultInitrdPath + } + instr.Annots["com.urunc.unikernel.unikernelType"] = hops.Platform.Framework + instr.Annots["com.urunc.unikernel.cmdline"] = hops.Cmd + instr.Annots["com.urunc.unikernel.hypervisor"] = hops.Platform.Monitor + if hops.Platform.Version != "" { + instr.Annots["com.urunc.unikernel.unikernelVersion"] = hops.Platform.Version + } + + return instr, nil +} + +// CheckBunnyfileVersion checks if the version of the user's input file +// is compatible with the supported version. +func CheckBunnyfileVersion(userVersion string) error { + if userVersion == "" { + return fmt.Errorf("The version field is necessary") + } + hopsVersion, err := version.NewVersion(bunnyFileVersion) + if err != nil { + return fmt.Errorf("Internal error in current bunnyfile version %s: %v", bunnyFileVersion, err) + } + userFileVer, err := version.NewVersion(userVersion) + if err != nil { + return fmt.Errorf("Could not parse version in user bunnyfile %s: %v", userVersion, err) + } + if hopsVersion.LessThan(userFileVer) { + return fmt.Errorf("Unsupported version %s. Please use %s or earlier", userVersion, bunnyFileVersion) + } + + return nil +} + +// ParseBunnyFile reads a yaml file which contains instructions for +// bunny. +func ParseBunnyFile(fileBytes []byte) (*PackInstructions, error) { + var bunnyHops Hops + + err := yaml.Unmarshal(fileBytes, &bunnyHops) + if err != nil { + return nil, err + } + + err = CheckBunnyfileVersion(bunnyHops.Version) + if err != nil { + return nil, err + } + + if bunnyHops.Platform.Framework == "" { + return nil, fmt.Errorf("The framework field of platforms is necessary") + } + + if bunnyHops.Platform.Monitor == "" { + return nil, fmt.Errorf("The monitor field of platforms is necessary") + } + + if bunnyHops.Kernel.From == "" { + return nil, fmt.Errorf("The from field of kernel is necessary") + } + + if bunnyHops.Kernel.Path == "" { + return nil, fmt.Errorf("The path field of kernel is necessary") + } + + return HopsToPack(bunnyHops) +} + // ParseDockerFile reads a Dockerfile-like file and returns a Hops // struct with the info from the file func ParseDockerFile(fileBytes []byte) (*PackInstructions, error) {