Skip to content

Commit

Permalink
Allow plugins to define custom containerfile commands
Browse files Browse the repository at this point in the history
  • Loading branch information
axtloss committed Nov 2, 2024
1 parent 425d6d3 commit d02e161
Show file tree
Hide file tree
Showing 13 changed files with 101 additions and 143 deletions.
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

0 comments on commit d02e161

Please sign in to comment.