Skip to content

Commit

Permalink
feat: implement finalize plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
axtloss committed Aug 9, 2024
1 parent 079709e commit 74beccc
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 66 deletions.
24 changes: 18 additions & 6 deletions api/finalize-scopes.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion core/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
14 changes: 14 additions & 0 deletions core/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"os/exec"

"github.com/mitchellh/mapstructure"
"github.com/vanilla-os/vib/api"
)

Expand Down Expand Up @@ -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
Expand Down
221 changes: 164 additions & 57 deletions core/plugins.in
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
}
6 changes: 6 additions & 0 deletions core/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
24 changes: 22 additions & 2 deletions finalize-plugins/docker-remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}

0 comments on commit 74beccc

Please sign in to comment.