Skip to content
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
1 change: 1 addition & 0 deletions .ssyncignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.git
266 changes: 147 additions & 119 deletions builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,158 +30,186 @@ import (
// Can also be passed as a build arg hence needs to be accessed from commands
const AdditionalPackageBuildArg = "ADDITIONAL_PACKAGE"

// BuildImage construct Docker image from function parameters
// TODO: refactor signature to a struct to simplify the length of the method header
func BuildImage(image string, handler string, functionName string, language string, nocache bool, squash bool, shrinkwrap bool, buildArgMap map[string]string, buildOptions []string, tagFormat schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, remoteBuilder, payloadSecretPath string, forcePull bool) error {
func getTemplate(lang string) (string, *stack.LanguageTemplate, error) {

if stack.IsValidTemplate(language) {
pathToTemplateYAML := fmt.Sprintf("./template/%s/template.yml", language)
if _, err := os.Stat(pathToTemplateYAML); err != nil && os.IsNotExist(err) {
return err
}
cwd, err := os.Getwd()
if err != nil {
return "", nil, fmt.Errorf("can't get current working directory: %w", err)
}

langTemplate, err := stack.ParseYAMLForLanguageTemplate(pathToTemplateYAML)
if err != nil {
return fmt.Errorf("error reading language template: %s", err.Error())
}
templateDir := filepath.Join(cwd, "template")
if _, err := os.Stat(templateDir); err != nil {
return "", nil, fmt.Errorf("template directory not found")
}

mountSSH := false
if langTemplate.MountSSH {
mountSSH = true
}
files, err := os.ReadDir(templateDir)
if err != nil {
return "", nil, fmt.Errorf("can't read template directory: %w", err)
}

if err := ensureHandlerPath(handler); err != nil {
return fmt.Errorf("building %s, %s is an invalid path", functionName, handler)
found := ""
for _, file := range files {
if file.IsDir() {
if file.Name() == lang {
found = filepath.Join(templateDir, file.Name())
break
}
}
}

opts := []builder.BuildContextOption{}
if len(langTemplate.HandlerFolder) > 0 {
opts = append(opts, builder.WithHandlerOverlay(langTemplate.HandlerFolder))
}
if len(found) == 0 {
return "", nil, fmt.Errorf("template %s not found", lang)
}
parsed, err := stack.ParseYAMLForLanguageTemplate(filepath.Join(found, "template.yml"))
if err != nil {
return "", nil, fmt.Errorf("can't parse template: %w", err)
}
return found, parsed, nil
}

buildContext, err := builder.CreateBuildContext(functionName, handler, language, copyExtraPaths, opts...)
if err != nil {
return err
}
// BuildImage construct Docker image from function parameters
// TODO: refactor signature to a struct to simplify the length of the method header
func BuildImage(image string, handler string, functionName string, language string, nocache bool, squash bool, shrinkwrap bool, buildArgMap map[string]string, buildOptions []string, tagFormat schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, remoteBuilder, payloadSecretPath string, forcePull bool) error {

if shrinkwrap {
fmt.Printf("%s shrink-wrapped to %s\n", functionName, buildContext)
return nil
}
_, langTemplate, err := getTemplate(language)
if err != nil {
return fmt.Errorf("language template: %s not supported, build a custom Dockerfile, error: %w", language, err)
}

branch, version, err := GetImageTagValues(tagFormat, handler)
if err != nil {
return err
}
mountSSH := false
if langTemplate.MountSSH {
mountSSH = true
}

imageName := schema.BuildImageName(tagFormat, image, version, branch)
if err := ensureHandlerPath(handler); err != nil {
return fmt.Errorf("building %s, %s is an invalid path", functionName, handler)
}

buildOptPackages, err := getBuildOptionPackages(buildOptions, language, langTemplate.BuildOptions)
if err != nil {
return err
opts := []builder.BuildContextOption{}
if len(langTemplate.HandlerFolder) > 0 {
opts = append(opts, builder.WithHandlerOverlay(langTemplate.HandlerFolder))
}

}
buildArgMap = appendAdditionalPackages(buildArgMap, buildOptPackages)
buildContext, err := builder.CreateBuildContext(functionName, handler, language, copyExtraPaths, opts...)
if err != nil {
return err
}

fmt.Printf("Building: %s with %s template. Please wait..\n", imageName, language)
if shrinkwrap {
fmt.Printf("%s shrink-wrapped to %s\n", functionName, buildContext)
return nil
}

if remoteBuilder != "" {
tempDir, err := os.MkdirTemp(os.TempDir(), "openfaas-build-*")
if err != nil {
return fmt.Errorf("failed to create temporary directory: %w", err)
}
defer os.RemoveAll(tempDir)
branch, version, err := GetImageTagValues(tagFormat, handler)
if err != nil {
return err
}

tarPath := path.Join(tempDir, "req.tar")
imageName := schema.BuildImageName(tagFormat, image, version, branch)

buildConfig := builder.BuildConfig{
Image: imageName,
BuildArgs: buildArgMap,
}
buildOptPackages, err := getBuildOptionPackages(buildOptions, language, langTemplate.BuildOptions)
if err != nil {
return err

// Prepare a tar archive that contains the build config and build context.
if err := builder.MakeTar(tarPath, path.Join("build", functionName), &buildConfig); err != nil {
return fmt.Errorf("failed to create tar file for %s, error: %w", functionName, err)
}
}
buildArgMap = appendAdditionalPackages(buildArgMap, buildOptPackages)

// Get the HMAC secret used for payload authentication with the builder API.
payloadSecret, err := os.ReadFile(payloadSecretPath)
if err != nil {
return fmt.Errorf("failed to read payload secret: %w", err)
}
payloadSecret = bytes.TrimSpace(payloadSecret)
fmt.Printf("Building: %s with %s template. Please wait..\n", imageName, language)

// Initialize a new builder client.
u, _ := url.Parse(remoteBuilder)
builderURL := &url.URL{
Scheme: u.Scheme,
Host: u.Host,
}
b := builder.NewFunctionBuilder(builderURL, http.DefaultClient, builder.WithHmacAuth(string(payloadSecret)))
if remoteBuilder != "" {
tempDir, err := os.MkdirTemp(os.TempDir(), "openfaas-build-*")
if err != nil {
return fmt.Errorf("failed to create temporary directory: %w", err)
}
defer os.RemoveAll(tempDir)

stream, err := b.BuildWithStream(tarPath)
if err != nil {
return fmt.Errorf("failed to invoke builder: %w", err)
}
defer stream.Close()
tarPath := path.Join(tempDir, "req.tar")

for result := range stream.Results() {
if !quietBuild {
for _, logMsg := range result.Log {
fmt.Printf("%s\n", logMsg)
}
}
buildConfig := builder.BuildConfig{
Image: imageName,
BuildArgs: buildArgMap,
}

// Prepare a tar archive that contains the build config and build context.
if err := builder.MakeTar(tarPath, path.Join("build", functionName), &buildConfig); err != nil {
return fmt.Errorf("failed to create tar file for %s, error: %w", functionName, err)
}

// Get the HMAC secret used for payload authentication with the builder API.
payloadSecret, err := os.ReadFile(payloadSecretPath)
if err != nil {
return fmt.Errorf("failed to read payload secret: %w", err)
}
payloadSecret = bytes.TrimSpace(payloadSecret)

// Initialize a new builder client.
u, _ := url.Parse(remoteBuilder)
builderURL := &url.URL{
Scheme: u.Scheme,
Host: u.Host,
}
b := builder.NewFunctionBuilder(builderURL, http.DefaultClient, builder.WithHmacAuth(string(payloadSecret)))

switch result.Status {
case builder.BuildSuccess:
log.Printf("%s success building and pushing image: %s", functionName, result.Image)
case builder.BuildFailed:
return fmt.Errorf("%s failure while building or pushing image %s: %s", functionName, imageName, result.Error)
stream, err := b.BuildWithStream(tarPath)
if err != nil {
return fmt.Errorf("failed to invoke builder: %w", err)
}
defer stream.Close()

for result := range stream.Results() {
if !quietBuild {
for _, logMsg := range result.Log {
fmt.Printf("%s\n", logMsg)
}
}

} else {
dockerBuildVal := dockerBuild{
Image: imageName,
NoCache: nocache,
Squash: squash,
HTTPProxy: os.Getenv("http_proxy"),
HTTPSProxy: os.Getenv("https_proxy"),
BuildArgMap: buildArgMap,
BuildLabelMap: buildLabelMap,
ForcePull: forcePull,
switch result.Status {
case builder.BuildSuccess:
log.Printf("%s success building and pushing image: %s", functionName, result.Image)
case builder.BuildFailed:
return fmt.Errorf("%s failure while building or pushing image %s: %s", functionName, imageName, result.Error)
}
}

command, args := getDockerBuildCommand(dockerBuildVal)
} else {
dockerBuildVal := dockerBuild{
Image: imageName,
NoCache: nocache,
Squash: squash,
HTTPProxy: os.Getenv("http_proxy"),
HTTPSProxy: os.Getenv("https_proxy"),
BuildArgMap: buildArgMap,
BuildLabelMap: buildLabelMap,
ForcePull: forcePull,
}

envs := os.Environ()
if mountSSH {
envs = append(envs, "DOCKER_BUILDKIT=1")
}
log.Printf("Build flags: %+v\n", args)

task := v2execute.ExecTask{
Cwd: buildContext,
Command: command,
Args: args,
StreamStdio: !quietBuild,
Env: envs,
}
command, args := getDockerBuildCommand(dockerBuildVal)

res, err := task.Execute(context.TODO())
envs := os.Environ()
if mountSSH {
envs = append(envs, "DOCKER_BUILDKIT=1")
}
log.Printf("Build flags: %+v\n", args)

task := v2execute.ExecTask{
Cwd: buildContext,
Command: command,
Args: args,
StreamStdio: !quietBuild,
Env: envs,
}

if err != nil {
return err
}
res, err := task.Execute(context.TODO())

if res.ExitCode != 0 {
return fmt.Errorf("[%s] received non-zero exit code from build, error: %s", functionName, res.Stderr)
}
if err != nil {
return err
}

fmt.Printf("Image: %s built.\n", imageName)
if res.ExitCode != 0 {
return fmt.Errorf("[%s] received non-zero exit code from build, error: %s", functionName, res.Stderr)
}
} else {
return fmt.Errorf("language template: %s not supported, build a custom Dockerfile", language)

fmt.Printf("Image: %s built.\n", imageName)
}

return nil
Expand Down
4 changes: 2 additions & 2 deletions builder/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func copyDir(src, dest string) error {
return fmt.Errorf("error reading dest stats: %s", err.Error())
}

if err := os.MkdirAll(dest, info.Mode()); err != nil {
return fmt.Errorf("error creating path: %s - %s", dest, err.Error())
if err := os.MkdirAll(dest, info.Mode()); err != nil && !os.IsExist(err) {
return fmt.Errorf("error creating directory: %s - %w", dest, err)
}

infos, err := ioutil.ReadDir(src)
Expand Down
Loading
Loading