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

Allow plugins to define custom containerfile commands #119

Merged
merged 1 commit into from
Nov 2, 2024
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
30 changes: 15 additions & 15 deletions api/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
137 changes: 37 additions & 100 deletions core/build.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package core

import (
"errors"
"fmt"
"os"
"strings"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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,
})
}
Expand All @@ -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...)
}
}

Expand All @@ -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
}
45 changes: 32 additions & 13 deletions core/plugins.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,28 @@ import (
"github.com/vanilla-os/vib/api"
)
import (
"encoding/base64"
"os"
"syscall"
)

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)
Expand All @@ -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{}
Expand All @@ -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")
Expand All @@ -62,48 +77,51 @@ 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)
}
pluginOpened := false
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)
}
}

Expand All @@ -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
}
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion core/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,5 @@ func BuildShellModule(moduleInterface interface{}, recipe *api.Recipe) (string,
}
}

return cmd, nil
return "RUN " + cmd, nil
}
Loading