From d02e1618b465ce2c79d347100f9e85014892258d Mon Sep 17 00:00:00 2001 From: axtloss Date: Wed, 30 Oct 2024 11:25:32 +0100 Subject: [PATCH] Allow plugins to define custom containerfile commands --- api/structs.go | 30 ++++---- core/build.go | 137 ++++++++++------------------------- core/plugins.in | 45 ++++++++---- core/shell.go | 2 +- core/structs.go | 4 +- plugins/apt.go | 2 +- plugins/cmake.go | 2 +- plugins/dpkg-buildpackage.go | 2 +- plugins/flatpak.go | 4 +- plugins/go.go | 2 +- plugins/make.go | 2 +- plugins/meson.go | 10 +-- plugins/shim.go | 2 +- 13 files changed, 101 insertions(+), 143 deletions(-) diff --git a/api/structs.go b/api/structs.go index 5e10b7e..0ce3711 100644 --- a/api/structs.go +++ b/api/structs.go @@ -29,19 +29,18 @@ type Recipe struct { // Configuration for a stage in the recipe type Stage struct { - Id string `json:"id"` - Base string `json:"base"` - SingleLayer bool `json:"singlelayer"` - Copy []Copy `json:"copy"` - Labels map[string]string `json:"labels"` - Env map[string]string `json:"env"` - Adds []Add `json:"adds"` - Args map[string]string `json:"args"` - Runs Run `json:"runs"` - Expose map[string]string `json:"expose"` - Cmd Cmd `json:"cmd"` - Modules []interface{} `json:"modules"` - Entrypoint Entrypoint + Id string `json:"id"` + Base string `json:"base"` + Copy []Copy `json:"copy"` + Labels map[string]string `json:"labels"` + Env map[string]string `json:"env"` + Adds []Add `json:"adds"` + Args map[string]string `json:"args"` + Runs Run `json:"runs"` + Expose map[string]string `json:"expose"` + Cmd Cmd `json:"cmd"` + Modules []interface{} `json:"modules"` + Entrypoint Entrypoint } type PluginType int @@ -53,8 +52,9 @@ const ( // Information about a plugin type PluginInfo struct { - Name string - Type PluginType + Name string + Type PluginType + UseContainerCmds bool } // Configuration for copying files or directories in a stage diff --git a/core/build.go b/core/build.go index 049b5ef..e6d613b 100644 --- a/core/build.go +++ b/core/build.go @@ -1,7 +1,6 @@ package core import ( - "errors" "fmt" "os" "strings" @@ -172,27 +171,25 @@ func BuildContainerfile(recipe *api.Recipe) error { } // RUN(S) - if !stage.SingleLayer { - if len(stage.Runs.Commands) > 0 { - err = ChangeWorkingDirectory(stage.Runs.Workdir, containerfile) - if err != nil { - return err - } - - for _, cmd := range stage.Runs.Commands { - _, err = containerfile.WriteString( - fmt.Sprintf("RUN %s\n", cmd), - ) - if err != nil { - return err - } - } + if len(stage.Runs.Commands) > 0 { + err = ChangeWorkingDirectory(stage.Runs.Workdir, containerfile) + if err != nil { + return err + } - err = RestoreWorkingDirectory(stage.Runs.Workdir, containerfile) + for _, cmd := range stage.Runs.Commands { + _, err = containerfile.WriteString( + fmt.Sprintf("RUN %s\n", cmd), + ) if err != nil { return err } } + + err = RestoreWorkingDirectory(stage.Runs.Workdir, containerfile) + if err != nil { + return err + } } // EXPOSE @@ -243,74 +240,20 @@ func BuildContainerfile(recipe *api.Recipe) error { return err } - // MODULES RUN(S) - if !stage.SingleLayer { - for _, cmd := range cmds { - if cmd.Command == "" { - continue - } - - err = ChangeWorkingDirectory(cmd.Workdir, containerfile) - if err != nil { - return err - } - - _, err = containerfile.WriteString( - fmt.Sprintf("RUN %s\n", cmd.Command), - ) - if err != nil { - return err - } - - err = RestoreWorkingDirectory(cmd.Workdir, containerfile) - if err != nil { - return err - } + for _, cmd := range cmds { + err = ChangeWorkingDirectory(cmd.Workdir, containerfile) + if err != nil { + return err } - } - - // SINGLE LAYER - if stage.SingleLayer { - if len(stage.Runs.Commands) > 0 { - err = ChangeWorkingDirectory(stage.Runs.Workdir, containerfile) - if err != nil { - return err - } - - unifiedCmd := "RUN " - - for i, cmd := range stage.Runs.Commands { - unifiedCmd += cmd - if i != len(stage.Runs.Commands)-1 { - unifiedCmd += " && " - } - } - - if len(cmds) > 0 { - unifiedCmd += " && " - } - - for i, cmd := range cmds { - if cmd.Workdir != stage.Runs.Workdir { - return errors.New("Workdir mismatch") - } - unifiedCmd += cmd.Command - if i != len(cmds)-1 { - unifiedCmd += " && " - } - } - if len(unifiedCmd) > 4 { - _, err = containerfile.WriteString(fmt.Sprintf("%s\n", unifiedCmd)) - if err != nil { - return err - } - } - - err = RestoreWorkingDirectory(stage.Runs.Workdir, containerfile) - if err != nil { - return err - } + _, err = containerfile.WriteString(strings.Join(cmd.Command, "\n")) + if err != nil { + return err + } + fmt.Println(strings.Join(cmd.Command, "----")) + err = RestoreWorkingDirectory(cmd.Workdir, containerfile) + if err != nil { + return err } } @@ -375,7 +318,7 @@ func BuildModules(recipe *api.Recipe, modules []interface{}) ([]ModuleCommand, e cmds = append(cmds, ModuleCommand{ Name: module.Name, - Command: cmd, + Command: append(cmd, ""), // add empty entry to ensure proper newline in Containerfile Workdir: module.Workdir, }) } @@ -384,23 +327,23 @@ func BuildModules(recipe *api.Recipe, modules []interface{}) ([]ModuleCommand, e } // Build a command string for the given module in the recipe -func BuildModule(recipe *api.Recipe, moduleInterface interface{}) (string, error) { +func BuildModule(recipe *api.Recipe, moduleInterface interface{}) ([]string, error) { var module Module err := mapstructure.Decode(moduleInterface, &module) if err != nil { - return "", err + return []string{""}, err } fmt.Printf("Building module [%s] of type [%s]\n", module.Name, module.Type) - var commands string + var commands []string if len(module.Modules) > 0 { for _, nestedModule := range module.Modules { buildModule, err := BuildModule(recipe, nestedModule) if err != nil { - return "", err + return []string{""}, err } - commands = commands + " && " + buildModule + commands = append(commands, buildModule...) } } @@ -412,23 +355,17 @@ func BuildModule(recipe *api.Recipe, moduleInterface interface{}) (string, error if moduleBuilder, ok := moduleBuilders[module.Type]; ok { command, err := moduleBuilder(moduleInterface, recipe) if err != nil { - return "", err + return []string{""}, err } - commands = commands + " && " + command + commands = append(commands, command) } else { command, err := LoadBuildPlugin(module.Type, moduleInterface, recipe) if err != nil { - return "", err + return []string{""}, err } - commands = commands + " && " + command + commands = append(commands, command...) } fmt.Printf("Module [%s] built successfully\n", module.Name) - result := strings.TrimPrefix(commands, " && ") - - if result == "&&" { - return "", nil - } - - return result, nil + return commands, nil } diff --git a/core/plugins.in b/core/plugins.in index 83a871a..2adf306 100644 --- a/core/plugins.in +++ b/core/plugins.in @@ -10,6 +10,7 @@ import ( "github.com/vanilla-os/vib/api" ) import ( + "encoding/base64" "os" "syscall" ) @@ -17,7 +18,20 @@ import ( var openedBuildPlugins map[string]Plugin var openedFinalizePlugins map[string]Plugin -func LoadPlugin(name string, plugintype api.PluginType, recipe *api.Recipe) (uintptr, error) { +func decodeBuildCmds(cmds string) ([]string, error) { + splitCmds := strings.Split(cmds, ",") + resCmds := []string{} + for _, cmd := range splitCmds { + decodedCmd, err := base64.StdEncoding.DecodeString(cmd) + if err != nil { + return []string{}, err + } + resCmds = append(resCmds, string(decodedCmd)) + } + return resCmds, nil +} + +func LoadPlugin(name string, plugintype api.PluginType, recipe *api.Recipe) (uintptr, api.PluginInfo, error) { fmt.Println("Loading new plugin") localPluginPath := fmt.Sprintf("%s/%s.so", recipe.PluginPath, name) @@ -42,7 +56,7 @@ func LoadPlugin(name string, plugintype api.PluginType, recipe *api.Recipe) (uin infoLoc, err := purego.Dlsym(loadedPlugin, "PlugInfo") if err != nil && !strings.Contains(err.Error(), "undefined symbol: PlugInfo") { fmt.Println(err) - return loadedPlugin, err + return loadedPlugin, api.PluginInfo{}, err } pluginInfo := &api.PluginInfo{} @@ -54,6 +68,7 @@ func LoadPlugin(name string, plugintype api.PluginType, recipe *api.Recipe) (uin fmt.Println("== WARN ==") pluginInfo.Name = name pluginInfo.Type = api.BuildPlugin + pluginInfo.UseContainerCmds = false } else { var pluginInfoFunc func() string purego.RegisterLibFunc(&pluginInfoFunc, loadedPlugin, "PlugInfo") @@ -62,15 +77,15 @@ func LoadPlugin(name string, plugintype api.PluginType, recipe *api.Recipe) (uin if pluginInfo.Type != plugintype { if plugintype == api.BuildPlugin { - return loadedPlugin, fmt.Errorf("ERROR: Plugin %s is not of type BuildPlugin", name) + return loadedPlugin, *pluginInfo, 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, *pluginInfo, fmt.Errorf("ERROR: Plugin %s is not of type FinalizePlugin", name) } } - return loadedPlugin, nil + return loadedPlugin, *pluginInfo, nil } -func LoadBuildPlugin(name string, module interface{}, recipe *api.Recipe) (string, error) { +func LoadBuildPlugin(name string, module interface{}, recipe *api.Recipe) ([]string, error) { if openedBuildPlugins == nil { openedBuildPlugins = make(map[string]Plugin) } @@ -78,32 +93,35 @@ func LoadBuildPlugin(name string, module interface{}, recipe *api.Recipe) (strin var buildModule Plugin buildModule, pluginOpened = openedBuildPlugins[name] if !pluginOpened { - loadedPlugin, err := LoadPlugin(name, api.BuildPlugin, recipe) + loadedPlugin, pluginInfo, err := LoadPlugin(name, api.BuildPlugin, recipe) if err != nil { - return "", err + return []string{""}, err } var buildFunction func(*C.char, *C.char) string purego.RegisterLibFunc(&buildFunction, loadedPlugin, "BuildModule") buildModule.Name = name buildModule.BuildFunc = buildFunction buildModule.LoadedPlugin = loadedPlugin + buildModule.PluginInfo = pluginInfo openedBuildPlugins[name] = buildModule } fmt.Printf("Using plugin: %s\n", buildModule.Name) moduleJson, err := json.Marshal(module) if err != nil { - return "", err + return []string{""}, err } recipeJson, err := json.Marshal(recipe) if err != nil { - return "", err + return []string{""}, err } res := buildModule.BuildFunc(C.CString(string(moduleJson)), C.CString(string(recipeJson))) if strings.HasPrefix(res, "ERROR:") { - return "", fmt.Errorf("%s", strings.Replace(res, "ERROR: ", "", 1)) + return []string{""}, fmt.Errorf("%s", strings.Replace(res, "ERROR: ", "", 1)) + } else if !buildModule.PluginInfo.UseContainerCmds { + return []string{"RUN " + res}, nil } else { - return res, nil + return decodeBuildCmds(res) } } @@ -115,7 +133,7 @@ func LoadFinalizePlugin(name string, module interface{}, recipe *api.Recipe, run var finalizeModule Plugin finalizeModule, pluginOpened = openedFinalizePlugins[name] if !pluginOpened { - loadedPlugin, err := LoadPlugin(name, api.FinalizePlugin, recipe) + loadedPlugin, pluginInfo, err := LoadPlugin(name, api.FinalizePlugin, recipe) if err != nil { return err } @@ -124,6 +142,7 @@ func LoadFinalizePlugin(name string, module interface{}, recipe *api.Recipe, run finalizeModule.Name = name finalizeModule.BuildFunc = finalizeFunction finalizeModule.LoadedPlugin = loadedPlugin + finalizeModule.PluginInfo = pluginInfo openedFinalizePlugins[name] = finalizeModule } fmt.Printf("Using Finalize plugin: %s\n", finalizeModule.Name) diff --git a/core/shell.go b/core/shell.go index 9b2f062..ed66566 100644 --- a/core/shell.go +++ b/core/shell.go @@ -51,5 +51,5 @@ func BuildShellModule(moduleInterface interface{}, recipe *api.Recipe) (string, } } - return cmd, nil + return "RUN " + cmd, nil } diff --git a/core/structs.go b/core/structs.go index bc56dbb..96adb19 100644 --- a/core/structs.go +++ b/core/structs.go @@ -1,6 +1,7 @@ package core import "C" +import "github.com/vanilla-os/vib/api" // Configuration for a module type Module struct { @@ -28,7 +29,7 @@ type IncludesModule struct { // Information for building a module type ModuleCommand struct { Name string - Command string + Command []string Workdir string } @@ -37,4 +38,5 @@ type Plugin struct { Name string BuildFunc func(*C.char, *C.char) string LoadedPlugin uintptr + PluginInfo api.PluginInfo } diff --git a/plugins/apt.go b/plugins/apt.go index 6e5aee2..4435c54 100644 --- a/plugins/apt.go +++ b/plugins/apt.go @@ -32,7 +32,7 @@ type AptOptions struct { // //export PlugInfo func PlugInfo() *C.char { - plugininfo := &api.PluginInfo{Name: "apt", Type: api.BuildPlugin} + plugininfo := &api.PluginInfo{Name: "apt", Type: api.BuildPlugin, UseContainerCmds: false} pluginjson, err := json.Marshal(plugininfo) if err != nil { return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) diff --git a/plugins/cmake.go b/plugins/cmake.go index 1562c04..677a85b 100644 --- a/plugins/cmake.go +++ b/plugins/cmake.go @@ -22,7 +22,7 @@ type CMakeModule struct { // //export PlugInfo func PlugInfo() *C.char { - plugininfo := &api.PluginInfo{Name: "cmake", Type: api.BuildPlugin} + plugininfo := &api.PluginInfo{Name: "cmake", Type: api.BuildPlugin, UseContainerCmds: false} pluginjson, err := json.Marshal(plugininfo) if err != nil { return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) diff --git a/plugins/dpkg-buildpackage.go b/plugins/dpkg-buildpackage.go index 5f92dbd..a4c56f1 100644 --- a/plugins/dpkg-buildpackage.go +++ b/plugins/dpkg-buildpackage.go @@ -20,7 +20,7 @@ type DpkgBuildModule struct { // //export PlugInfo func PlugInfo() *C.char { - plugininfo := &api.PluginInfo{Name: "dpkg-buildpackage", Type: api.BuildPlugin} + plugininfo := &api.PluginInfo{Name: "dpkg-buildpackage", Type: api.BuildPlugin, UseContainerCmds: false} pluginjson, err := json.Marshal(plugininfo) if err != nil { return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) diff --git a/plugins/flatpak.go b/plugins/flatpak.go index 74580da..cc0fb0c 100644 --- a/plugins/flatpak.go +++ b/plugins/flatpak.go @@ -66,7 +66,7 @@ WantedBy=default.target // //export PlugInfo func PlugInfo() *C.char { - plugininfo := &api.PluginInfo{Name: "flatpak", Type: api.BuildPlugin} + plugininfo := &api.PluginInfo{Name: "flatpak", Type: api.BuildPlugin, UseContainerCmds: false} pluginjson, err := json.Marshal(plugininfo) if err != nil { return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) @@ -88,7 +88,7 @@ func createRepo(module innerFlatpakModule, isSystem bool) string { } // Generate setup commands for Flatpak module configuration. -// Create scripts for system-wide and user-specific Flatpak setups, +// Create scripts for system-wide and user-specific Flatpak setups, // including repository addition, package installation, and service configuration. // //export BuildModule diff --git a/plugins/go.go b/plugins/go.go index c7d2eaf..9bfa791 100644 --- a/plugins/go.go +++ b/plugins/go.go @@ -21,7 +21,7 @@ type GoModule struct { // //export PlugInfo func PlugInfo() *C.char { - plugininfo := &api.PluginInfo{Name: "go", Type: api.BuildPlugin} + plugininfo := &api.PluginInfo{Name: "go", Type: api.BuildPlugin, UseContainerCmds: false} pluginjson, err := json.Marshal(plugininfo) if err != nil { return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) diff --git a/plugins/make.go b/plugins/make.go index fa47e92..bf441d2 100644 --- a/plugins/make.go +++ b/plugins/make.go @@ -23,7 +23,7 @@ type MakeModule struct { // //export PlugInfo func PlugInfo() *C.char { - plugininfo := &api.PluginInfo{Name: "make", Type: api.BuildPlugin} + plugininfo := &api.PluginInfo{Name: "make", Type: api.BuildPlugin, UseContainerCmds: false} pluginjson, err := json.Marshal(plugininfo) if err != nil { return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) diff --git a/plugins/meson.go b/plugins/meson.go index eb03cc3..920d77e 100644 --- a/plugins/meson.go +++ b/plugins/meson.go @@ -11,17 +11,17 @@ import ( // Configuration for building a Meson project type MesonModule struct { - Name string - Type string - BuildFlags []string `json:"buildflags"` - Source api.Source + Name string + Type string + BuildFlags []string `json:"buildflags"` + Source api.Source } // Provide plugin information as a JSON string // //export PlugInfo func PlugInfo() *C.char { - plugininfo := &api.PluginInfo{Name: "meson", Type: api.BuildPlugin} + plugininfo := &api.PluginInfo{Name: "meson", Type: api.BuildPlugin, UseContainerCmds: false} pluginjson, err := json.Marshal(plugininfo) if err != nil { return C.CString(fmt.Sprintf("ERROR: %s", err.Error())) diff --git a/plugins/shim.go b/plugins/shim.go index bc37ee9..55164a8 100644 --- a/plugins/shim.go +++ b/plugins/shim.go @@ -25,7 +25,7 @@ type ShimModule struct { // //export PlugInfo func PlugInfo() *C.char { - plugininfo := &api.PluginInfo{Name: "shim", Type: api.BuildPlugin} + plugininfo := &api.PluginInfo{Name: "shim", Type: api.BuildPlugin, UseContainerCmds: false} pluginjson, err := json.Marshal(plugininfo) if err != nil { return C.CString(fmt.Sprintf("ERROR: %s", err.Error()))