core API

core

package

API reference for the core package.

S
struct

ShellModule

Configuration for shell modules

core/shell.go:12-17
type ShellModule struct

Fields

Name Type Description
Name string json:"name"
Type string json:"type"
Sources []api.Source
Commands []string
F
function

BuildShellModule

Build shell module commands and return them as a single string

Parameters

moduleInterface
interface{}
recipe
arch
string

Returns

string
error
core/shell.go:22-57
func BuildShellModule(moduleInterface interface{}, recipe *api.Recipe, arch string) (string, error)

{
	var module ShellModule
	err := mapstructure.Decode(moduleInterface, &module)
	if err != nil {
		return "", err
	}

	for _, source := range module.Sources {
		if api.TestArch(source.OnlyArches, arch) {
			if strings.TrimSpace(source.Type) != "" {
				err := api.DownloadSource(recipe, source, module.Name)
				if err != nil {
					return "", err
				}
				err = api.MoveSource(recipe.DownloadsPath, recipe.SourcesPath, source, module.Name)
				if err != nil {
					return "", err
				}
			}
		}
	}

	if len(module.Commands) == 0 {
		return "", errors.New("no commands specified")
	}

	cmd := ""
	for i, command := range module.Commands {
		cmd += command
		if i < len(module.Commands)-1 {
			cmd += " && "
		}
	}

	return "RUN " + cmd, nil
}
S
struct

Module

Configuration for a module

core/structs.go:7-13
type Module struct

Fields

Name Type Description
Name string json:"name"
Workdir string
Type string json:"type"
Modules []map[string]interface{}
Content []byte
S
struct

Finalize

Configuration for finalization steps

core/structs.go:16-20
type Finalize struct

Fields

Name Type Description
Name string json:"name"
Type string json:"type"
Content []byte
S
struct

IncludesModule

Configuration for including other modules or recipes

core/structs.go:23-27
type IncludesModule struct

Fields

Name Type Description
Name string json:"name"
Type string json:"type"
Includes []string json:"includes"
S
struct

ModuleCommand

Information for building a module

core/structs.go:30-34
type ModuleCommand struct

Fields

Name Type Description
Name string
Command []string
Workdir string
S
struct

Plugin

Configuration for a plugin

core/structs.go:37-42
type Plugin struct

Fields

Name Type Description
Name string
BuildFunc func(*C.char, *C.char, *C.char) string
LoadedPlugin uintptr
PluginInfo api.PluginInfo
F
function

ChangeWorkingDirectory

Add a WORKDIR instruction to the containerfile

Parameters

workdir
string
containerfile

Returns

error
core/build.go:15-25
func ChangeWorkingDirectory(workdir string, containerfile *os.File) error

{
	if workdir != "" {
		_, err := containerfile.WriteString(
			fmt.Sprintf("WORKDIR %s\n", workdir),
		)
		if err != nil {
			return err
		}
	}
	return nil
}
F
function

RestoreWorkingDirectory

Add a WORKDIR instruction to reset to the root directory

Parameters

workdir
string
containerfile

Returns

error
core/build.go:28-39
func RestoreWorkingDirectory(workdir string, containerfile *os.File) error

{
	if workdir != "" {
		_, err := containerfile.WriteString(
			fmt.Sprintf("WORKDIR %s\n", "/"),
		)
		if err != nil {
			return err
		}
	}

	return nil
}
F
function

BuildRecipe

Load and build a Containerfile from the specified recipe

Parameters

recipePath
string
arch
string

Returns

error
core/build.go:42-67
func BuildRecipe(recipePath string, arch string) (api.Recipe, error)

{
	// load the recipe
	recipe, err := LoadRecipe(recipePath)
	if err != nil {
		return api.Recipe{}, err
	}

	fmt.Printf("Building recipe %s\n", recipe.Name)

	// build the Containerfile
	err = BuildContainerfile(recipe, arch)
	if err != nil {
		return api.Recipe{}, err
	}

	modules := 0
	for _, stage := range recipe.Stages {
		modules += len(stage.Modules)
	}

	fmt.Printf("Recipe %s built successfully\n", recipe.Name)
	fmt.Printf("Processed %d stages\n", len(recipe.Stages))
	fmt.Printf("Processed %d modules\n", modules)

	return *recipe, nil
}
F
function

BuildContainerfile

Generate a Containerfile from the recipe

Parameters

recipe
arch
string

Returns

error
core/build.go:70-319
func BuildContainerfile(recipe *api.Recipe, arch string) error

{
	containerfile, err := os.Create(recipe.Containerfile)
	if err != nil {
		return err
	}

	defer containerfile.Close()

	for _, stage := range recipe.Stages {
		// build the modules*
		// * actually just build the commands that will be used
		//   in the Containerfile to build the modules
		cmds, err := BuildModules(recipe, stage.Modules, arch, stage.Id)
		if err != nil {
			return err
		}

		// FROM
		if stage.Id != "" {
			_, err = containerfile.WriteString(
				fmt.Sprintf("# Stage: %s\n", stage.Id),
			)
			if err != nil {
				return err
			}
			_, err = containerfile.WriteString(
				fmt.Sprintf("FROM %s AS %s\n", stage.Base, stage.Id),
			)
			if err != nil {
				return err
			}
		} else {
			_, err = containerfile.WriteString(
				fmt.Sprintf("FROM %s\n", stage.Base),
			)
			if err != nil {
				return err
			}
		}

		// COPY
		if len(stage.Copy) > 0 {
			for _, copy := range stage.Copy {
				if len(copy.SrcDst) > 0 {
					err = ChangeWorkingDirectory(copy.Workdir, containerfile)
					if err != nil {
						return err
					}

					for src, dst := range copy.SrcDst {
						if copy.From != "" {
							_, err = containerfile.WriteString(
								fmt.Sprintf("COPY --from=%s %s %s\n", copy.From, src, dst),
							)
							if err != nil {
								return err
							}
						} else {
							_, err = containerfile.WriteString(
								fmt.Sprintf("COPY %s %s\n", src, dst),
							)
							if err != nil {
								return err
							}
						}
					}

					err = RestoreWorkingDirectory(copy.Workdir, containerfile)
					if err != nil {
						return err
					}
				}
			}
		}

		// LABELS
		for key, value := range stage.Labels {
			_, err = containerfile.WriteString(
				fmt.Sprintf("LABEL %s='%s'\n", key, value),
			)
			if err != nil {
				return err
			}
		}

		// ENV
		for key, value := range stage.Env {
			_, err = containerfile.WriteString(
				fmt.Sprintf("ENV %s=%s\n", key, value),
			)
			if err != nil {
				return err
			}
		}

		// ARGS
		for key, value := range stage.Args {
			_, err = containerfile.WriteString(
				fmt.Sprintf("ARG %s=%s\n", key, value),
			)
			if err != nil {
				return err
			}
		}

		// RUN(S)
		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
				}
			}

			err = RestoreWorkingDirectory(stage.Runs.Workdir, containerfile)
			if err != nil {
				return err
			}
		}

		// EXPOSE
		for key, value := range stage.Expose {
			_, err = containerfile.WriteString(
				fmt.Sprintf("EXPOSE %s/%s\n", key, value),
			)
			if err != nil {
				return err
			}
		}

		// ADDS
		if len(stage.Adds) > 0 {
			for _, add := range stage.Adds {
				if len(add.SrcDst) > 0 {
					err = ChangeWorkingDirectory(add.Workdir, containerfile)
					if err != nil {
						return err
					}

					for src, dst := range add.SrcDst {
						_, err = containerfile.WriteString(
							fmt.Sprintf("ADD %s %s\n", src, dst),
						)
						if err != nil {
							return err
						}
					}
				}

				err = RestoreWorkingDirectory(add.Workdir, containerfile)
				if err != nil {
					return err
				}
			}
		}

		// INCLUDES.CONTAINER
		if stage.Addincludes {
			_, err = containerfile.WriteString(fmt.Sprintf("ADD %s /\n", recipe.IncludesPath))
			if err != nil {
				return err
			}
		}

		// SOURCES
		sourcePath := filepath.Join("sources", stage.Id)
		err = os.MkdirAll(sourcePath, 0o755)
		if err != nil {
			return fmt.Errorf("could not create source path: %w", err)
		}
		_, err = containerfile.WriteString(fmt.Sprintf("ADD %s /sources\n", sourcePath))
		if err != nil {
			return err
		}

		for _, cmd := range cmds {
			err = ChangeWorkingDirectory(cmd.Workdir, containerfile)
			if err != nil {
				return err
			}

			_, err = containerfile.WriteString(strings.Join(cmd.Command, "\n"))
			if err != nil {
				return err
			}

			err = RestoreWorkingDirectory(cmd.Workdir, containerfile)
			if err != nil {
				return err
			}
		}

		// CMD
		err = ChangeWorkingDirectory(stage.Cmd.Workdir, containerfile)
		if err != nil {
			return err
		}

		if len(stage.Cmd.Exec) > 0 {
			_, err = containerfile.WriteString(
				fmt.Sprintf("CMD [\"%s\"]\n", strings.Join(stage.Cmd.Exec, "\",\"")),
			)
			if err != nil {
				return err
			}

			err = RestoreWorkingDirectory(stage.Cmd.Workdir, containerfile)
			if err != nil {
				return err
			}
		}

		// DELETE SOURCES
		_, err = containerfile.WriteString("RUN rm -r /sources\n")
		if err != nil {
			return err
		}

		// ENTRYPOINT
		err = ChangeWorkingDirectory(stage.Entrypoint.Workdir, containerfile)
		if err != nil {
			return err
		}

		if len(stage.Entrypoint.Exec) > 0 {
			_, err = containerfile.WriteString(
				fmt.Sprintf("ENTRYPOINT [\"%s\"]\n", strings.Join(stage.Entrypoint.Exec, "\",\"")),
			)
			if err != nil {
				return err
			}

			err = RestoreWorkingDirectory(stage.Entrypoint.Workdir, containerfile)
			if err != nil {
				return err
			}
		}

		containerfile.WriteString("\n")
	}

	return nil
}
F
function

BuildModules

Build commands for each module in the recipe

Parameters

recipe
modules
[]interface{}
arch
string
stageName
string

Returns

error
core/build.go:322-344
func BuildModules(recipe *api.Recipe, modules []interface{}, arch string, stageName string) ([]ModuleCommand, error)

{
	cmds := []ModuleCommand{}
	for _, moduleInterface := range modules {
		var module Module
		err := mapstructure.Decode(moduleInterface, &module)
		if err != nil {
			return nil, err
		}

		cmd, err := BuildModule(recipe, moduleInterface, arch, stageName)
		if err != nil {
			return nil, err
		}

		cmds = append(cmds, ModuleCommand{
			Name:    module.Name,
			Command: append(cmd, ""), // add empty entry to ensure proper newline in Containerfile
			Workdir: module.Workdir,
		})
	}

	return cmds, nil
}
F
function

buildIncludesModule

Parameters

moduleInterface
interface{}
recipe
arch
string
stageName
string

Returns

string
error
core/build.go:346-393
func buildIncludesModule(moduleInterface interface{}, recipe *api.Recipe, arch string, stageName string) (string, error)

{
	var include IncludesModule
	err := mapstructure.Decode(moduleInterface, &include)
	if err != nil {
		return "", err
	}

	if len(include.Includes) == 0 {
		return "", errors.New("includes module must have at least one module to include")
	}

	var commands []string
	for _, include := range include.Includes {
		var modulePath string

		// in case of a remote include, we need to download the
		// recipe before including it
		if include[:4] == "http" {
			fmt.Printf("Downloading recipe from %s\n", include)
			modulePath, err = downloadRecipe(include)
			if err != nil {
				return "", err
			}
		} else if followsGhPattern(include) {
			// if the include follows the github pattern, we need to
			// download the recipe from the github repository
			fmt.Printf("Downloading recipe from %s\n", include)
			modulePath, err = downloadGhRecipe(include)
			if err != nil {
				return "", err
			}
		} else {
			modulePath = filepath.Join(recipe.ParentPath, include)
		}

		includeModule, err := GenModule(modulePath)
		if err != nil {
			return "", err
		}

		buildModule, err := BuildModule(recipe, includeModule, arch, stageName)
		if err != nil {
			return "", err
		}
		commands = append(commands, buildModule...)
	}
	return strings.Join(commands, "\n"), nil
}
F
function

BuildModule

Build a command string for the given module in the recipe

Parameters

recipe
moduleInterface
interface{}
arch
string
stageName
string

Returns

[]string
error
core/build.go:396-451
func BuildModule(recipe *api.Recipe, moduleInterface interface{}, arch string, stageName string) ([]string, error)

{
	var module Module
	err := mapstructure.Decode(moduleInterface, &module)
	if err != nil {
		return []string{""}, err
	}

	fmt.Printf("Building module [%s] of type [%s]\n", module.Name, module.Type)

	commands := []string{fmt.Sprintf("\n# Begin Module %s - %s", module.Name, module.Type)}

	if len(module.Modules) > 0 {
		for _, nestedModule := range module.Modules {
			buildModule, err := BuildModule(recipe, nestedModule, arch, stageName)
			if err != nil {
				return []string{""}, err
			}
			commands = append(commands, buildModule...)
		}
	}

	switch module.Type {
	case "shell":
		command, err := BuildShellModule(moduleInterface, recipe, arch)
		if err != nil {
			return []string{""}, err
		}
		commands = append(commands, command)
	case "includes":
		command, err := buildIncludesModule(moduleInterface, recipe, arch, stageName)
		if err != nil {
			return []string{""}, err
		}
		commands = append(commands, command)
	default:
		command, err := LoadBuildPlugin(module.Type, moduleInterface, recipe, arch)
		if err != nil {
			return []string{""}, err
		}
		commands = append(commands, command...)
	}

	sourcePath := filepath.Join(recipe.SourcesPath, module.Name)
	stageSourcePath := filepath.Join(recipe.SourcesPath, stageName, module.Name)
	_ = os.MkdirAll(sourcePath, 0o777)
	_ = os.MkdirAll(filepath.Dir(stageSourcePath), 0o777)
	err = os.Rename(sourcePath, stageSourcePath)
	if err != nil {
		return []string{}, fmt.Errorf("could not move source: %w", err)
	}

	commands = append(commands, fmt.Sprintf("# End Module %s - %s\n", module.Name, module.Type))

	fmt.Printf("Module [%s] built successfully\n", module.Name)
	return commands, nil
}
F
function

CompileRecipe

Compile and build the recipe using the specified runtime

Parameters

recipePath
string
arch
string
runtime
string
isRoot
bool
origGid
int
origUid
int

Returns

error
core/compile.go:14-57
func CompileRecipe(recipePath string, arch string, runtime string, isRoot bool, origGid int, origUid int) error

{
	recipe, err := BuildRecipe(recipePath, arch)
	if err != nil {
		return err
	}

	syscall.Setegid(0)
	syscall.Seteuid(0)
	switch runtime {
	case "docker":
		err = compileDocker(recipe, origGid, origUid)
		if err != nil {
			return err
		}
	case "podman":
		err = compilePodman(recipe, origGid, origUid)
		if err != nil {
			return err
		}
	case "buildah":
		return fmt.Errorf("buildah not implemented yet")
	default:
		return fmt.Errorf("no runtime specified and the prometheus library is not implemented yet")
	}
	syscall.Setegid(origGid)
	syscall.Seteuid(origUid)

	for _, finalizeInterface := range recipe.Finalize {
		var module Finalize

		err := mapstructure.Decode(finalizeInterface, &module)
		if err != nil {
			return err
		}
		err = LoadFinalizePlugin(module.Type, finalizeInterface, &recipe, arch, runtime, isRoot, origGid, origUid)
		if err != nil {
			return err
		}
	}

	fmt.Printf("Image %s built successfully using %s\n", recipe.Id, runtime)

	return nil
}
F
function

compileDocker

Build an OCI image using the specified recipe through Docker

Parameters

recipe
gid
int
uid
int

Returns

error
core/compile.go:60-77
func compileDocker(recipe api.Recipe, gid int, uid int) error

{
	docker, err := exec.LookPath("docker")
	if err != nil {
		return err
	}

	cmd := exec.Command(
		docker, "build",
		"-t", fmt.Sprintf("localhost/%s", recipe.Id),
		"-f", recipe.Containerfile,
		".",
	)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = recipe.ParentPath

	return cmd.Run()
}
F
function

compilePodman

Build an OCI image using the specified recipe through Podman

Parameters

recipe
gid
int
uid
int

Returns

error
core/compile.go:80-97
func compilePodman(recipe api.Recipe, gid int, uid int) error

{
	podman, err := exec.LookPath("podman")
	if err != nil {
		return err
	}

	cmd := exec.Command(
		podman, "build",
		"-t", fmt.Sprintf("localhost/%s", recipe.Id),
		"-f", recipe.Containerfile,
		".",
	)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = recipe.ParentPath

	return cmd.Run()
}
S
struct

StorageConf

Configuration for storage drivers

core/finalize.go:11-15
type StorageConf struct

Fields

Name Type Description
Driver string
Runroot string
Graphroot string
F
function

GetContainerStorage

Retrieve the container storage configuration based on the runtime

Parameters

runtime
string

Returns

core/finalize.go:18-53
func GetContainerStorage(runtime string) (cstorage.Store, error)

{
	storageconfig := &StorageConf{}
	if runtime == "podman" {
		podmanPath, err := exec.LookPath("podman")
		out, err := exec.Command(
			podmanPath, "info", "-f json").Output()
		if err != nil {
			fmt.Println("Failed to get podman info")
		} else {
			driver := strings.Split(strings.Split(string(out), "\"graphDriverName\": \"")[1], "\",")[0]
			storageconfig.Driver = driver

			graphRoot := strings.Split(strings.Split(string(out), "\"graphRoot\": \"")[1], "\",")[0]
			storageconfig.Graphroot = graphRoot

			runRoot := strings.Split(strings.Split(string(out), "\"runRoot\": \"")[1], "\",")[0]
			storageconfig.Runroot = runRoot
		}

	}
	if storageconfig.Runroot == "" {
		storageconfig.Runroot = "/var/lib/vib/runroot"
		storageconfig.Graphroot = "/var/lib/vib/graphroot"
		storageconfig.Driver = "overlay"
	}
	store, err := cstorage.GetStore(cstorage.StoreOptions{
		RunRoot:         storageconfig.Runroot,
		GraphRoot:       storageconfig.Graphroot,
		GraphDriverName: storageconfig.Driver,
	})
	if err != nil {
		return store, err
	}

	return store, err
}
F
function

GetImageID

Retrieve the image ID for a given image name from the storage

Parameters

name
string

Returns

string
error
core/finalize.go:56-69
func GetImageID(name string, store cstorage.Store) (string, error)

{
	images, err := store.Images()
	if err != nil {
		return "", err
	}
	for _, img := range images {
		for _, imgname := range img.Names {
			if imgname == name {
				return img.ID, nil
			}
		}
	}
	return "", fmt.Errorf("image not found")
}
F
function

GetTopLayerID

Retrieve the top layer ID for a given image ID from the storage

Parameters

imageid
string

Returns

string
error
core/finalize.go:72-83
func GetTopLayerID(imageid string, store cstorage.Store) (string, error)

{
	images, err := store.Images()
	if err != nil {
		return "", err
	}
	for _, img := range images {
		if img.ID == imageid {
			return img.TopLayer, nil
		}
	}
	return "", fmt.Errorf("no top layer for id %s found", imageid)
}
F
function

MountImage

Mount the image and return the mount directory

Parameters

imagename
string
imageid
string
runtime
string

Returns

string
error
core/finalize.go:86-100
func MountImage(imagename string, imageid string, runtime string) (string, error)

{
	store, err := GetContainerStorage(runtime)
	if err != nil {
		return "", err
	}
	topLayerID, err := GetTopLayerID(imageid, store)
	if err != nil {
		return "", err
	}
	mountDir, err := store.Mount(topLayerID, "")
	if err != nil {
		return "", err
	}
	return mountDir, err
}
F
function

LoadRecipe

LoadRecipe loads a recipe from a file and returns a Recipe
Does not validate the recipe but it will catch some errors
a proper validation will be done in the future

Parameters

path
string

Returns

error
core/loader.go:22-155
func LoadRecipe(path string) (*api.Recipe, error)

{
	recipe := &api.Recipe{}

	// we use the absolute path to the recipe file as the
	// root path for the recipe and all its files
	recipePath, err := filepath.Abs(path)
	if err != nil {
		return nil, err
	}

	// here we open the recipe file and unmarshal it into
	// the Recipe struct, this is not a full validation
	// but it will catch some errors
	recipeFile, err := os.Open(recipePath)
	if err != nil {
		return nil, err
	}
	defer recipeFile.Close()

	recipeYAML, err := io.ReadAll(recipeFile)
	if err != nil {
		return nil, err
	}

	err = yaml.Unmarshal(recipeYAML, recipe)
	if err != nil {
		return nil, err
	}

	if len(strings.TrimSpace(recipe.Vibversion)) <= 0 {
		return nil, fmt.Errorf("version key not found in recipe file, assuming outdated recipe")
	}

	recipeVersionS := strings.Split(recipe.Vibversion, ".")
	if len(recipeVersionS) != 3 {
		return nil, fmt.Errorf("invalid version format, expected x.x.x, got %s", recipe.Vibversion)
	}

	recipeVersion := []uint8{0, 0, 0}
	for i := 0; i < len(recipeVersion); i++ {
		versionInt, err := strconv.ParseUint(recipeVersionS[i], 10, 8)
		if err != nil {
			return nil, err
		}
		if versionInt > math.MaxUint8 {
			recipeVersion[i] = math.MaxUint8
		} else {
			recipeVersion[i] = uint8(versionInt)
		}
	}

	if recipeVersion[0] < Min_Recipe_Version[0] || recipeVersion[1] < Min_Recipe_Version[1] || recipeVersion[2] < Min_Recipe_Version[2] {
		return nil, fmt.Errorf("outdated recipe, this version of vib supports recipes starting at version %s", strings.Join(strings.Fields(fmt.Sprint(Min_Recipe_Version)), "."))
	}

	// the recipe path is stored in the recipe itself
	// for convenience
	recipe.Path = recipePath
	recipe.ParentPath = filepath.Dir(recipePath)

	// assuming the Containerfile location is relative
	recipe.Containerfile = filepath.Join(filepath.Dir(recipePath), "Containerfile")
	err = os.RemoveAll(recipe.Containerfile)
	if err != nil {
		return nil, err
	}

	// we create the sources directory which is the place where
	// all the sources will be stored and be available to all
	// the modules
	recipe.SourcesPath = filepath.Join(filepath.Dir(recipePath), "sources")
	err = os.RemoveAll(recipe.SourcesPath)
	if err != nil {
		return nil, err
	}
	err = os.MkdirAll(recipe.SourcesPath, 0755)
	if err != nil {
		return nil, err
	}

	// the downloads directory is a transient directory, here all
	// the downloaded sources will be stored before being moved
	// to the sources directory. This is useful since some sources
	// types need to be extracted, this way we can extract them
	// directly to the sources directory after downloading them
	recipe.DownloadsPath = filepath.Join(filepath.Dir(recipePath), "downloads")
	err = os.RemoveAll(recipe.DownloadsPath)
	if err != nil {
		return nil, err
	}
	err = os.MkdirAll(recipe.DownloadsPath, 0755)
	if err != nil {
		return nil, err
	}

	// the plugins directory contains all plugins that vib can load
	// and use for unknown modules in the recipe
	recipe.PluginPath = filepath.Join(filepath.Dir(recipePath), "plugins")

	// the includes directory is the place where we store all the
	// files to be included in the container, this is useful for
	// example to include configuration files. Each file must follow
	// the File Hierarchy Standard (FHS) and be placed in the correct
	// directory. For example, if you want to include a file in
	// /etc/nginx/nginx.conf you must place it in includes/etc/nginx/nginx.conf
	// so it will be copied to the correct location in the container
	if len(strings.TrimSpace(recipe.IncludesPath)) == 0 {
		recipe.IncludesPath = filepath.Join("includes.container")
	}
	_, err = os.Stat(recipe.IncludesPath)
	if os.IsNotExist(err) {
		err := os.MkdirAll(recipe.IncludesPath, 0755)
		if err != nil {
			return nil, err
		}
	}

	for i, stage := range recipe.Stages {
		// here we check if the extra Adds path exists
		for _, add := range stage.Adds {
			for src := range add.SrcDst {
				fullPath := filepath.Join(filepath.Dir(recipePath), src)
				_, err = os.Stat(fullPath)
				if os.IsNotExist(err) {
					return nil, err
				}
			}
		}

		recipe.Stages[i] = stage
	}

	return recipe, nil
}
F
function

downloadRecipe

downloadRecipe downloads a recipe from a remote URL and stores it to
a temporary file

Parameters

url
string

Returns

path
string
err
error
core/loader.go:159-178
func downloadRecipe(url string) (path string, err error)

{
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	tmpFile, err := os.CreateTemp("", "vib-recipe-")
	if err != nil {
		return "", err
	}
	defer tmpFile.Close()

	_, err = io.Copy(tmpFile, resp.Body)
	if err != nil {
		return "", err
	}

	return tmpFile.Name(), nil
}
F
function

followsGhPattern

followsGhPattern checks if a given path follows the pattern:
gh:org/repo:branch:path

Parameters

s
string

Returns

bool
core/loader.go:182-193
func followsGhPattern(s string) bool

{
	parts := strings.Split(s, ":")
	if len(parts) != 4 {
		return false
	}

	if parts[0] != "gh" {
		return false
	}

	return true
}
F
function

downloadGhRecipe

downloadGhRecipe downloads a recipe from a github repository and stores it to
a temporary file

Parameters

gh
string

Returns

path
string
err
error
core/loader.go:197-205
func downloadGhRecipe(gh string) (path string, err error)

{
	parts := strings.Split(gh, ":")
	repo := parts[1]
	branch := parts[2]
	file := parts[3]

	url := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s", repo, branch, file)
	return downloadRecipe(url)
}
F
function

GenModule

GenModule generate a Module struct from a module path

Parameters

modulePath
string

Returns

map[string]interface{}
error
core/loader.go:208-228
func GenModule(modulePath string) (map[string]interface{}, error)

{
	var module map[string]interface{}

	moduleFile, err := os.Open(modulePath)
	if err != nil {
		return module, err
	}
	defer moduleFile.Close()

	moduleYAML, err := io.ReadAll(moduleFile)
	if err != nil {
		return module, err
	}

	err = yaml.Unmarshal(moduleYAML, &module)
	if err != nil {
		return module, err
	}

	return module, nil
}
F
function

TestRecipe

TestRecipe validates a recipe by loading it and checking for errors

Parameters

path
string

Returns

error
core/loader.go:231-247
func TestRecipe(path string) (*api.Recipe, error)

{
	recipe, err := LoadRecipe(path)
	if err != nil {
		fmt.Printf("Error validating recipe: %s\n", err)
		return nil, err
	}

	modules := 0
	for _, stage := range recipe.Stages {
		modules += len(stage.Modules)
	}

	fmt.Printf("Recipe %s validated successfully\n", recipe.Id)
	fmt.Printf("Found %d stages\n", len(recipe.Stages))
	fmt.Printf("Found %d modules\n", modules)
	return recipe, nil
}