Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small refactor of the code for better structure, error propagation and preperation for extentions #2

Merged
merged 3 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ LINT_CNTR_OPTS ?= $(CNTR_OPTS) -v $(CURDIR):/app -w /app
LINT_CNTR_IMG ?= golangci/golangci-lint:v1.53.3
LINT_CNTR_CMD ?= golangci-lint run -v --timeout=5m

# Source files variables
#
# Add all urunc specific go packages as dependency for building
# or the shimAny change ina go file will result to rebuilding urunc
BUNNY_SRC := $(wildcard $(CURDIR)/cmd/*.go)
BUNNY_SRC += $(wildcard $(CURDIR)/hops/*.go)

# Main Building rules
#
# By default we opt to build static binaries targeting the host archiotecture.
Expand All @@ -74,10 +81,10 @@ $(VENDOR_DIR):
# vendor do notproduce any file and execute all the time,
# we avoid the rebuilding of urunc if it has previously built and the
# source files have not changed.
$(BUNNY_BIN): main.go | prepare
$(BUNNY_BIN): $(BUNNY_SRC) | prepare
$(GO_FLAGS) $(GO) build \
-ldflags "$(LDFLAGS_COMMON) $(LDFLAGS_STATIC) $(LDFLAGS_OPT)" \
-o $(BUNNY_BIN)
-o $(BUNNY_BIN) $(CURDIR)/cmd

## install Install urunc and shim in PREFIX
.PHONY: install
Expand Down
178 changes: 31 additions & 147 deletions main.go → cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,25 @@
package main

import (
"encoding/base64"
"encoding/json"
"context"
"flag"
"os"
"fmt"
"bytes"
"strings"
"io/ioutil"

"bunny/hops"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
"github.com/moby/buildkit/frontend/gateway/grpcclient"
"github.com/moby/buildkit/util/appcontext"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/frontend/gateway/client"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/moby/buildkit/frontend/dockerfile/instructions"
"github.com/moby/buildkit/frontend/dockerfile/parser"
)

const (
unikraftKernelPath string = "/unikraft/bin/kernel"
unikraftHub string = "unikraft.org"
packContextName string = "context"
buildContextName string = "context"
clientOptFilename string = "filename"
uruncJSONPath string = "/urunc.json"
)

type CLIOpts struct {
Expand All @@ -53,12 +46,6 @@ type CLIOpts struct {
PrintLLB bool
}

type PackInstructions struct {
Base string // The Base image to use
Copies []instructions.CopyCommand // Copy commands
Annots map[string]string // Annotations
}

var version string

func usage() {
Expand Down Expand Up @@ -86,113 +73,9 @@ func parseCLIOpts() CLIOpts {
return opts
}

func parseFile(fileBytes []byte) (*PackInstructions, error) {
var instr *PackInstructions
instr = new(PackInstructions)
instr.Annots = make(map[string]string)

r := bytes.NewReader(fileBytes)

// Parse the Dockerfile
parseRes, err := parser.Parse(r)
if err != nil {
fmt.Printf("Failed to parse file: %v\n", err)
return nil, err
}

// Traverse Dockerfile commands
for _, child := range parseRes.AST.Children {
cmd, err := instructions.ParseInstruction(child)
if err != nil {
fmt.Printf("Failed to parse instruction %s: %v\n", child.Value, err)
return nil, err
}
switch c := cmd.(type) {
case *instructions.Stage:
// Handle FROM
if instr.Base != "" {
return nil, fmt.Errorf("Multi-stage builds are not supported")
}
instr.Base = c.BaseName
case *instructions.CopyCommand:
// Handle COPY
instr.Copies = append(instr.Copies, *c)
case *instructions.LabelCommand:
// Handle LABLE annotations
for _, kvp := range c.Labels {
annotKey := strings.Trim(kvp.Key, "\"")
instr.Annots[annotKey] = strings.Trim(kvp.Value, "\"")
}
case instructions.Command:
// Catch all other commands
fmt.Printf("UNsupported command%s\n", c.Name())
default:
fmt.Printf("%f is not a command type\n", c)
}

}

return instr, nil
}

func copyIn(base llb.State, from string, src string, dst string) llb.State {
var copyState llb.State
var localSrc llb.State

localSrc = llb.Local(packContextName)
copyState = base.File(llb.Copy(localSrc, src, dst, &llb.CopyInfo{
CreateDestPath: true,}))

return copyState
}

func constructLLB(instr PackInstructions) (*llb.Definition, error) {
var base llb.State
uruncJSON := make(map[string]string)

// Create urunc.json file, since annotations do not reach urunc
for annot, val := range instr.Annots {
encoded := base64.StdEncoding.EncodeToString([]byte(val))
uruncJSON[annot] = string(encoded)
}
uruncJSONBytes, err := json.Marshal(uruncJSON)
if err != nil {
return nil, fmt.Errorf("Failed to marshal urunc json: %v", err)
}

// Set the base image where we will pack the unikernel
if instr.Base == "scratch" {
base = llb.Scratch()
} else if strings.HasPrefix(instr.Base, unikraftHub) {
// Define the platform to qemu/amd64 so we cna pull unikraft images
platform := ocispecs.Platform{
OS: "qemu",
Architecture: "amd64",
}
base = llb.Image(instr.Base, llb.Platform(platform),)
} else {
base = llb.Image(instr.Base)
}

// Perform any copies inside the image
for _, aCopy := range instr.Copies {
base = copyIn(base, packContextName, aCopy.SourcePaths[0], aCopy.DestPath)
}

// Create the urunc.json file in the rootfs
base = base.File(llb.Mkfile(uruncJSONPath, 0644, uruncJSONBytes))

dt, err := base.Marshal(context.TODO(), llb.LinuxAmd64)
if err != nil {
return nil, fmt.Errorf("Failed to marshal LLB state: %v", err)
}

return dt, nil
}

func readFileFromLLB(ctx context.Context, c client.Client, filename string) ([]byte, error) {
// Get the file from client's context
fileSrc := llb.Local(packContextName, llb.IncludePatterns([]string {filename}),
fileSrc := llb.Local(buildContextName, llb.IncludePatterns([]string {filename}),
llb.WithCustomName("Internal:Read-" + filename))
fileDef, err := fileSrc.Marshal(ctx)
if err != nil {
Expand All @@ -206,7 +89,7 @@ func readFileFromLLB(ctx context.Context, c client.Client, filename string) ([]b
}
fileRef, err := fileRes.SingleRef()
if err != nil {
return nil, fmt.Errorf("Failed to get ref from solve resutl for fetching %s: %w", clientOptFilename, err)
return nil, fmt.Errorf("Failed to get reference of result for fetching %s: %w", clientOptFilename, err)
}

// Read the content of the file
Expand All @@ -223,7 +106,7 @@ func readFileFromLLB(ctx context.Context, c client.Client, filename string) ([]b
func annotateRes(annots map[string]string, res *client.Result) (*client.Result, error) {
ref, err := res.SingleRef()
if err != nil {
return nil, fmt.Errorf("Failed te get reference of LLB solve result : %v",err)
return nil, fmt.Errorf("Failed te get reference build result: %v",err)
}

config := ocispecs.Image{
Expand All @@ -241,11 +124,11 @@ func annotateRes(annots map[string]string, res *client.Result) (*client.Result,
},
}

uruncJSONBytes, err := json.Marshal(config)
imageConfig, err := json.Marshal(config)
if err != nil {
return nil, fmt.Errorf("Failed to marshal urunc json: %v", err)
return nil, fmt.Errorf("Failed to marshal image config: %v", err)
}
res.AddMeta(exptypes.ExporterImageConfigKey, uruncJSONBytes)
res.AddMeta(exptypes.ExporterImageConfigKey, imageConfig)
for annot, val := range annots {
res.AddMeta(exptypes.AnnotationManifestKey(nil, annot), []byte(val))
}
Expand All @@ -256,30 +139,30 @@ func annotateRes(annots map[string]string, res *client.Result) (*client.Result,

func bunnyBuilder(ctx context.Context, c client.Client) (*client.Result, error) {
// Get the Build options from buildkit
packOpts := c.BuildOpts().Opts
buildOpts := c.BuildOpts().Opts

// Get the file that contains the instructions
packFile := packOpts[clientOptFilename]
if packFile == "" {
return nil, fmt.Errorf("%s: was not provided", clientOptFilename)
bunnyFile := buildOpts[clientOptFilename]
if bunnyFile == "" {
return nil, fmt.Errorf("Could not find %s", clientOptFilename)
}

// Fetch and read contents of user-specified file in build context
fileBytes, err := readFileFromLLB(ctx, c, packFile)
fileBytes, err := readFileFromLLB(ctx, c, bunnyFile)
if err != nil {
return nil, fmt.Errorf("Failed to fetch and read %s: %w", clientOptFilename, err)
}

// Parse packing instructions
packInst, err := parseFile(fileBytes)
// Parse packaging/building instructions
packInst, err := hops.ParseDockerFile(fileBytes)
if err != nil {
return nil, fmt.Errorf("Error parsing packing instructions", err)
return nil, fmt.Errorf("Error parsing building instructions: %v", err)
}

// Create the LLB definiton
dt, err := constructLLB(*packInst)
// Create the LLB definiton of packing the final image
dt, err := hops.PackLLB(*packInst, buildContextName)
if err != nil {
return nil, fmt.Errorf("Failed to create LLB definition : %v\n", err)
return nil, fmt.Errorf("Could not create LLB definition: %v", err)
}

// Pass LLB to buildkit
Expand All @@ -301,7 +184,7 @@ func bunnyBuilder(ctx context.Context, c client.Client) (*client.Result, error)

func main() {
var cliOpts CLIOpts
var packInst *PackInstructions
var packInst *hops.PackInstructions

cliOpts = parseCLIOpts()

Expand All @@ -314,36 +197,37 @@ func main() {
// Run as buildkit frontend
ctx := appcontext.Context()
if err := grpcclient.RunFromEnvironment(ctx, bunnyBuilder); err != nil {
fmt.Printf("Could not start grpcclient: %v\n", err)
fmt.Fprintf(os.Stderr, "Error: Could not connect to buildkit: %v\n", err)
os.Exit(1)
}

return
}

// Normal local execution to print LLB
if cliOpts.ContainerFile == "" {
fmt.Println("Please specify the Containerfile")
fmt.Println("Use -h or --help for more info")
fmt.Fprintf(os.Stderr, "Error: No instructions file as input\n")
fmt.Fprintf(os.Stderr, "Use -h or --help for more info\n")
os.Exit(1)
}

CntrFileContent, err := ioutil.ReadFile(cliOpts.ContainerFile)
if err != nil {
fmt.Printf("Failed to read %s: %v\n", cliOpts.ContainerFile, err)
fmt.Fprintf(os.Stderr, "Error: Could not read %s: %v\n", cliOpts.ContainerFile, err)
os.Exit(1)
}

// Parse file with packaging instructions
packInst, err = parseFile(CntrFileContent)
// Parse file with packaging/building instructions
packInst, err = hops.ParseDockerFile(CntrFileContent)
if err != nil {
fmt.Println("Error parsing packing instructions", err)
fmt.Fprintf(os.Stderr, "Error: Could not parse building instructions: %v\n", err)
os.Exit(1)
}

// Create the LLB definition
dt, err := constructLLB(*packInst)
// Create the LLB definition of packing the final image
dt, err := hops.PackLLB(*packInst, buildContextName)
if err != nil {
fmt.Printf("Failed to create LLB definition : %v\n", err)
fmt.Fprintf(os.Stderr, "Error: Could not create LLB definition: %v\n", err)
os.Exit(1)
}

Expand Down
Loading
Loading