From 74beccce47ed5089fb24ed9019ecbfde461a548c Mon Sep 17 00:00:00 2001 From: axtloss Date: Wed, 7 Aug 2024 22:43:54 +0200 Subject: [PATCH] feat: implement finalize plugins --- api/finalize-scopes.go | 24 +++- core/build.go | 2 +- core/compile.go | 14 ++ core/plugins.in | 221 ++++++++++++++++++++++-------- core/structs.go | 6 + finalize-plugins/docker-remote.go | 24 +++- 6 files changed, 225 insertions(+), 66 deletions(-) diff --git a/api/finalize-scopes.go b/api/finalize-scopes.go index 7e4938c..3b4c337 100644 --- a/api/finalize-scopes.go +++ b/api/finalize-scopes.go @@ -1,19 +1,31 @@ package api // / Get the final tagged Image name -var IMAEGNAME = 1 +var IMAGENAME int32 = 1 // / Get the final tagged Image ID -var IMAGEID = 2 +var IMAGEID int32 = 2 // / Get the build recipe -var RECIPE = 4 +var RECIPE int32 = 4 + +// Get the used build runtime +var RUNTIME int32 = 8 // / Get a read-write filesystem of the Image -var RWFS = 8 +var RWFS int32 = 16 // / Get a read-only filesystem of the Image -var ROFS = 16 +var ROFS int32 = 32 // / Prepare the filesystem to be chrooted into, requires either RWFilesystem or ROFilesystem -var CHROOTFS = 32 +var CHROOTFS int32 = 64 + +type ScopeData struct { + ImageName string + ImageID string + Recipe Recipe + Runtime string + RWFS string + ROFS string +} diff --git a/core/build.go b/core/build.go index 4e105d8..911aa3d 100644 --- a/core/build.go +++ b/core/build.go @@ -417,7 +417,7 @@ func BuildModule(recipe *api.Recipe, moduleInterface interface{}) (string, error } commands = commands + " && " + command } else { - command, err := LoadPlugin(module.Type, api.BuildPlugin, moduleInterface, recipe) + command, err := LoadBuildPlugin(module.Type, moduleInterface, recipe) if err != nil { return "", err } diff --git a/core/compile.go b/core/compile.go index 258d2a0..004f271 100644 --- a/core/compile.go +++ b/core/compile.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" + "github.com/mitchellh/mapstructure" "github.com/vanilla-os/vib/api" ) @@ -32,6 +33,19 @@ func CompileRecipe(recipePath string, runtime string) error { return fmt.Errorf("no runtime specified and the prometheus library is not implemented yet") } + for _, finalizeInterface := range recipe.Finalize { + var module Finalize + + err := mapstructure.Decode(finalizeInterface, &module) + if err != nil { + return err + } + err = LoadFinalizePlugin(module.Type, module, &recipe, runtime) + if err != nil { + return err + } + } + fmt.Printf("Image %s built successfully using %s\n", recipe.Id, runtime) return nil diff --git a/core/plugins.in b/core/plugins.in index 6014b52..60ec32f 100644 --- a/core/plugins.in +++ b/core/plugins.in @@ -9,75 +9,85 @@ import ( "github.com/ebitengine/purego" "github.com/vanilla-os/vib/api" ) -import "os" +import ( + "os" + "os/exec" +) -var openedPlugins map[string]Plugin +var openedBuildPlugins map[string]Plugin +var openedFinalizePlugins map[string]Plugin -func LoadPlugin(name string, plugintype api.PluginType, module interface{}, recipe *api.Recipe) (string, error) { - if openedPlugins == nil { - openedPlugins = make(map[string]Plugin) - } - pluginOpened := false - var buildModule Plugin - buildModule, pluginOpened = openedPlugins[name] - if !pluginOpened { - fmt.Println("Loading new plugin") - buildModule = Plugin{Name: name} - - localPluginPath := fmt.Sprintf("%s/%s.so", recipe.PluginPath, name) - - globalPluginPath := fmt.Sprintf("%INSTALLPREFIX%/share/vib/plugins/%s.so", name) - - // Prefer local plugins before global ones - var loadedPlugin uintptr - _, err := os.Stat(localPluginPath) - if os.IsNotExist(err) { - loadedPlugin, err = purego.Dlopen(globalPluginPath, purego.RTLD_NOW|purego.RTLD_GLOBAL) - if err != nil { - panic(err) // yayyy panics <3 - } - } else { - loadedPlugin, err = purego.Dlopen(localPluginPath, purego.RTLD_NOW|purego.RTLD_GLOBAL) - if err != nil { - panic(err) - } - } +func LoadPlugin(name string, plugintype api.PluginType, recipe *api.Recipe) (uintptr, error) { + fmt.Println("Loading new plugin") - infoLoc, err := purego.Dlsym(loadedPlugin, "PlugInfo") - if err != nil && !strings.Contains(err.Error(), "undefined symbol: PlugInfo") { - fmt.Println(err) - return "", err + localPluginPath := fmt.Sprintf("%s/%s.so", recipe.PluginPath, name) + + globalPluginPath := fmt.Sprintf("%INSTALLPREFIX%/share/vib/plugins/%s.so", name) + + // Prefer local plugins before global ones + var loadedPlugin uintptr + _, err := os.Stat(localPluginPath) + if os.IsNotExist(err) { + loadedPlugin, err = purego.Dlopen(globalPluginPath, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) // yayyy panics <3 + } + } else { + loadedPlugin, err = purego.Dlopen(localPluginPath, purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) } + } + + infoLoc, err := purego.Dlsym(loadedPlugin, "PlugInfo") + if err != nil && !strings.Contains(err.Error(), "undefined symbol: PlugInfo") { + fmt.Println(err) + return loadedPlugin, err + } - pluginInfo := &api.PluginInfo{} - - if infoLoc == 0 { - fmt.Println("== WARN ==") - fmt.Printf("Plugin %s does not contain function PlugInfo, assuming old BuildPlugin type\n", name) - fmt.Printf("Please update the plugin or request the developer of the plugin to update it!\n") - fmt.Println("== WARN ==") - pluginInfo.Name = name - pluginInfo.Type = api.BuildPlugin - } else { - var pluginInfoFunc func() string - purego.RegisterLibFunc(&pluginInfoFunc, loadedPlugin, "PlugInfo") - json.Unmarshal([]byte(pluginInfoFunc()), &pluginInfo) + pluginInfo := &api.PluginInfo{} + + if infoLoc == 0 { + fmt.Println("== WARN ==") + fmt.Printf("Plugin %s does not contain function PlugInfo, assuming old BuildPlugin type\n", name) + fmt.Printf("Please update the plugin or request the developer of the plugin to update it!\n") + fmt.Println("== WARN ==") + pluginInfo.Name = name + pluginInfo.Type = api.BuildPlugin + } else { + var pluginInfoFunc func() string + purego.RegisterLibFunc(&pluginInfoFunc, loadedPlugin, "PlugInfo") + json.Unmarshal([]byte(pluginInfoFunc()), &pluginInfo) + } + + if pluginInfo.Type != plugintype { + if plugintype == api.BuildPlugin { + return loadedPlugin, fmt.Errorf("ERROR: Plugin %s is not of type BuildPlugin", name) + } else if plugintype == api.FinalizePlugin { + return loadedPlugin, fmt.Errorf("ERROR: Plugin %s is not of type FinalizePlugin", name) } + } + return loadedPlugin, nil +} - if pluginInfo.Type != plugintype { - if plugintype == api.BuildPlugin { - fmt.Printf("ERROR: Plugin %s is not of type BuildPlugin", name) - os.Exit(1) - } else if plugintype == api.FinalizePlugin { - fmt.Printf("ERROR: Plugin %s is not of type FinalizePlugin", name) - os.Exit(1) - } +func LoadBuildPlugin(name string, module interface{}, recipe *api.Recipe) (string, error) { + if openedBuildPlugins == nil { + openedBuildPlugins = make(map[string]Plugin) + } + pluginOpened := false + var buildModule Plugin + buildModule, pluginOpened = openedBuildPlugins[name] + if !pluginOpened { + loadedPlugin, err := LoadPlugin(name, api.BuildPlugin, recipe) + if err != nil { + return "", err } var buildFunction func(*C.char, *C.char) string purego.RegisterLibFunc(&buildFunction, loadedPlugin, "BuildModule") + buildModule.Name = name buildModule.BuildFunc = buildFunction buildModule.LoadedPlugin = loadedPlugin - openedPlugins[name] = buildModule + openedBuildPlugins[name] = buildModule } fmt.Printf("Using plugin: %s\n", buildModule.Name) moduleJson, err := json.Marshal(module) @@ -96,3 +106,100 @@ func LoadPlugin(name string, plugintype api.PluginType, module interface{}, reci return res, nil } } + +func getImageID(name string, runtime string) (string, error) { + runtimePath, err := exec.LookPath(runtime) + if err != nil { + return "", err + } + + out, err := exec.Command( + runtimePath, "images", + "--format=\"{{json . }}\"", + ).Output() + if err != nil { + return "", err + } + for _, image := range strings.Split(string(out), "\n") { + if strings.Contains(image, fmt.Sprintf("{\"repository\":\"localhost/%s\",\"tag\":\"latest\"", name)) { + id := strings.Split(image, "\"Id\":\"")[1] + id = strings.Split(id, "\",")[0] + return id, nil + } + } + return "", fmt.Errorf("Unable to find id for: %s", name) + +} + +func LoadFinalizePlugin(name string, module interface{}, recipe *api.Recipe, runtime string) error { + if openedFinalizePlugins == nil { + openedFinalizePlugins = make(map[string]Plugin) + } + pluginOpened := false + var finalizeModule Plugin + finalizeModule, pluginOpened = openedFinalizePlugins[name] + if !pluginOpened { + loadedPlugin, err := LoadPlugin(name, api.FinalizePlugin, recipe) + if err != nil { + return err + } + var finalizeFunction func(*C.char, *C.char) string + purego.RegisterLibFunc(&finalizeFunction, loadedPlugin, "FinalizeBuild") + finalizeModule.Name = name + finalizeModule.BuildFunc = finalizeFunction + finalizeModule.LoadedPlugin = loadedPlugin + openedFinalizePlugins[name] = finalizeModule + } + fmt.Printf("Using Finalize plugin: %s\n", finalizeModule.Name) + + var getPluginScope func() int32 + purego.RegisterLibFunc(&getPluginScope, finalizeModule.LoadedPlugin, "PluginScope") + scope := getPluginScope() + fmt.Printf("\nFinalize Plugin %s registered scope:\n", finalizeModule.Name) + fmt.Printf("Get tagged image name: %t\n", (scope&api.IMAGENAME == api.IMAGENAME)) + fmt.Printf("Get tagged image id: %t\n", (scope&api.IMAGEID == api.IMAGEID)) + fmt.Printf("Get image recipe: %t\n", (scope&api.RECIPE == api.RECIPE)) + fmt.Printf("Get build runtime: %t\n", (scope&api.RUNTIME == api.RUNTIME)) + fmt.Printf("Create read-write filesystem of image: %t\n", (scope&api.RWFS == api.RWFS)) + fmt.Printf("Create read-only filesystem of image: %t\n", (scope&api.ROFS == api.ROFS)) + fmt.Printf("Prepare filesystem to be chrooted into: %t\n\n", (scope&api.CHROOTFS == api.CHROOTFS)) + scopedata := &api.ScopeData{} + if scope&api.IMAGENAME == api.IMAGENAME { + scopedata.ImageName = fmt.Sprintf("localhost/%s:latest", recipe.Id) + } + if scope&api.IMAGEID == api.IMAGEID { + imageID, err := getImageID(recipe.Id, runtime) + if err != nil { + return err + } + scopedata.ImageID = imageID + } + if scope&api.RECIPE == api.RECIPE { + scopedata.Recipe = *recipe + } + if scope&api.RUNTIME == api.RUNTIME { + scopedata.Runtime = runtime + } + if scope&api.RWFS == api.RWFS { + scopedata.RWFS = "TO BE IMPLEMENTED" + } else if scope&api.ROFS == api.ROFS { + scopedata.ROFS = "TO BE IMPLEMENTED" + } + if scope&api.CHROOTFS == api.CHROOTFS { + // TO BE IMPLEMENTED + } + moduleJson, err := json.Marshal(module) + if err != nil { + return err + } + scopeJson, err := json.Marshal(scopedata) + if err != nil { + return err + } + res := finalizeModule.BuildFunc(C.CString(string(moduleJson)), C.CString(string(scopeJson))) + if strings.HasPrefix(res, "ERROR:") { + return fmt.Errorf("%s", strings.Replace(res, "ERROR: ", "", 1)) + } else { + return nil + } +} \ No newline at end of file diff --git a/core/structs.go b/core/structs.go index 6443588..e8431dc 100644 --- a/core/structs.go +++ b/core/structs.go @@ -10,6 +10,12 @@ type Module struct { Content []byte // The entire module unparsed as a []byte, used by plugins } +type Finalize struct { + Name string `json:"name"` + Type string `json:"type"` + Content []byte // The entire module unparsed as a []byte, used by plugins +} + type IncludesModule struct { Name string `json:"name"` Type string `json:"type"` diff --git a/finalize-plugins/docker-remote.go b/finalize-plugins/docker-remote.go index 267885a..6e1b526 100644 --- a/finalize-plugins/docker-remote.go +++ b/finalize-plugins/docker-remote.go @@ -17,21 +17,41 @@ type DockerRemote struct { ImageTag string `json:"imagetag"` } +//export PlugInfo +func PlugInfo() *C.char { + plugininfo := &api.PluginInfo{Name: "docker-remote", Type: api.FinalizePlugin} + pluginjson, err := json.Marshal(plugininfo) + if err != nil { + return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) + } + return C.CString(string(pluginjson)) +} + //export PluginScope func PluginScope() int32 { // int32 is defined as GoInt32 in cgo which is the same as a C int - return int32(api.IMAEGNAME) + return api.IMAGENAME | api.RUNTIME | api.IMAGEID } //export FinalizeBuild func FinalizeBuild(moduleInterface *C.char, extraData *C.char) *C.char { var module *DockerRemote + var data *api.ScopeData err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module) if err != nil { return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) } - return C.CString("ERROR: failed to upload image") + err = json.Unmarshal([]byte(C.GoString(extraData)), &data) + if err != nil { + return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) + } + + fmt.Printf("Pushing %s with id %s\n", data.ImageName, data.ImageID) + fmt.Printf("%s push %s %s\n", data.Runtime, data.ImageName, module.OCIRegistry) + + return C.CString("") + // return C.CString("ERROR: failed to upload image") } func main() {}