Repository: openfaas/faas-cli (go)

Issue:
CLI release `0.16.32`: runtime error when invoking faas up
<!--- Provide a general summary of the issue in the Title above -->

## Expected Behaviour

With the version `0.16.30` of the CLI, the `faas up` command builds, pushes and deploys my function image as expected.

## Current Behaviour

With the latest faas-cli release `0.16.32` I get this error when invoking `faas up` 

```
[0] > Building customfunction.
Skipping "build" folder
Skipping "template" folder
Building: ghcr.io/alfranz/customfunction:latest with dockerfile template. Please wait..
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x79d2b8]

goroutine 5 [running]:
github.com/openfaas/faas-cli/builder.BuildImage({0xc0000ac300, 0x21}, {0xd2d870, 0x1}, {0xc000[14](https://github.com/alfranz/customfunction/actions/runs/[REDACTED]#step:6:15)7830, 0xa}, {0xc000147850, 0xa}, 0x0, 0x0, ...)
	/go/src/github.com/openfaas/faas-cli/builder/build.go:126 +0x878
github.com/openfaas/faas-cli/commands.build.func1(0x0)
	/go/src/github.com/openfaas/faas-cli/commands/build.go:251 +0x790
created by github.com/openfaas/faas-cli/commands.build in goroutine 1
	/go/src/github.com/openfaas/faas-cli/commands/build.go:240 +0xcb
Error: Process completed with exit code 2.
```

<!-- How is this affecting you? What task are you trying to accomplish? -->
## Why do you need this?

I want to deploy my function with the latest release of the CLI.

<!-- Attempts to mask or hide this may result in the issue being closed -->
## Who is this for?

What company is this for? Are you listed in the [ADOPTERS.md](https://github.com/openfaas/faas/blob/master/ADOPTERS.md) file?

## Are you a GitHub Sponsor (Yes/No?)

<!-- Issues created by customers or monthly sponsors get priority -->

Check at: https://github.com/sponsors/openfaas
- [ ] Yes
- [x] No

## List All Possible Solutions and Workarounds

Install older version of the CLI

```
curl -sSL https://github.com/openfaas/faas-cli/releases/download/0.16.30/faas-cli -o faas-cli && chmod +x faas-cli && sudo mv faas-cli /usr/local/bin/
```
<!--- Suggest a fix/reason for the bug, or ideas how to implement  -->
<!--- the addition or change -->
<!--- Is there a workaround which could avoid making changes? -->

## Which Solution Do You Recommend?

Install older CLI version.

## Steps to Reproduce (for bugs)

<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
2.
3.
4.

## Context
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->

## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
Running in Github Actions

```
Ubuntu 22.0[4] LTS
```

* FaaS-CLI version ( Full output from: `faas-cli version` ):

* Docker version ( Full output from: `docker version` ):

```
Run docker version 
Client: Docker Engine - Community
 Version:           26.1.3
 API version:       1.45
 Go version:        go1.21.10
 Git commit:        b[7](https://github.com/alfranz/supernotes/actions/runs/10284142475/job/28459542633#step:6:8)2abbb
 Built:             Thu May 16 0[8](https://github.com/alfranz/supernotes/actions/runs/10284142475/job/28459542633#step:6:9):33:29 2024
 OS/Arch:           linux/amd64
 Context:           default
Server: Docker Engine - Community
 Engine:
  Version:          26.1.3
  API version:      1.45 (minimum version 1.24)
  Go version:       go1.21.10
  Git commit:       8e[9](https://github.com/alfranz/supernotes/actions/runs/10284142475/job/28459542633#step:6:10)6db1
  Built:            Thu May 16 08:33:29 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.7.19
  GitCommit:        2bf793ef6dc9a18e00cb[12](https://github.com/alfranz/supernotes/actions/runs/10284142475/job/28459542633#step:6:13)efb64355c2c9d5eb41
 runc:
  Version:          1.7.19
  GitCommit:        v1.1.[13](https://github.com/alfranz/supernotes/actions/runs/10284142475/job/28459542633#step:6:14)-0-g58aa920
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
```

* Are you using Docker Swarm (FaaS-swarm ) or Kubernetes (FaaS-netes)? `faasd`

* Operating System and version (e.g. Linux, Windows, MacOS): Linux

* Link to your project or a code example to reproduce issue: Private


Pull request:
Revert changes to createBuildContext to fix #1001
<!--- Provide a general summary of your changes in the Title above -->

## Description
<!--- Describe your changes in detail -->

Fix nil pointer dereference when getting the build options packages.

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->
- [ ] I have raised an issue to propose this change ([required](https://github.com/openfaas/faas/blob/master/CONTRIBUTING.md))

Fixes #1001 

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

- Verified the `faas-cli up` command works for function using the dockerfile template.

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I've read the [CONTRIBUTION](https://github.com/openfaas/faas/blob/master/CONTRIBUTING.md) guide
- [x] I have signed-off my commits with `git commit -s`
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.


Patch:
diff --git a/builder/build.go b/builder/build.go
index 6a6cd093..27fb8be9 100644
--- a/builder/build.go
+++ b/builder/build.go
@@ -52,18 +52,7 @@ func BuildImage(image string, handler string, functionName string, language stri
 			return fmt.Errorf("building %s, %s is an invalid path", functionName, handler)
 		}
 
-		// To avoid breaking the CLI for custom templates that do not set the language attribute
-		// we ensure it is always set.
-		//
-		// While templates are expected to have the language in `template.yaml` set to the same name as the template folder
-		// this was never enforced.
-		langTemplate.Language = language
-
-		if isDockerfileTemplate(langTemplate.Language) {
-			langTemplate = nil
-		}
-
-		tempPath, err := CreateBuildContext(functionName, handler, langTemplate, copyExtraPaths)
+		tempPath, err := createBuildContext(functionName, handler, language, isLanguageTemplate(language), langTemplate.HandlerFolder, copyExtraPaths)
 		if err != nil {
 			return err
 		}
@@ -314,17 +303,8 @@ func isRunningInCI() bool {
 	return false
 }
 
-// CreateBuildContext creates a Docker build context using the provided function handler and language template.
-//
-// Parameters:
-//   - functionName: the name of the function.
-//   - handler: path to the function handler folder.
-//   - template: function language template to use. Set to nil if no template is required (e.g. the handler folder is the build context with Dockerfile).
-//   - copyExtraPaths: additional path to copy into the function handler folder. Paths should be relative to the current directory.
-//     Any path outside of this current directory will be skipped.
-//
-// Returns the path to the new build context. An error is returned if creating the build context fails.
-func CreateBuildContext(functionName string, handler string, template *stack.LanguageTemplate, copyExtraPaths []string) (string, error) {
+// createBuildContext creates temporary build folder to perform a Docker build with language template
+func createBuildContext(functionName string, handler string, language string, useFunction bool, handlerFolder string, copyExtraPaths []string) (string, error) {
 	tempPath := fmt.Sprintf("./build/%s/", functionName)
 
 	if err := os.RemoveAll(tempPath); err != nil {
@@ -333,19 +313,16 @@ func CreateBuildContext(functionName string, handler string, template *stack.Lan
 
 	functionPath := tempPath
 
-	useTemplate := false
-	if template != nil {
-		useTemplate = true
-	}
-
-	if useTemplate {
-		if len(template.HandlerFolder) > 0 {
-			functionPath = path.Join(functionPath, template.HandlerFolder)
-		} else {
+	if useFunction {
+		if handlerFolder == "" {
 			functionPath = path.Join(functionPath, defaultHandlerFolder)
+		} else {
+			functionPath = path.Join(functionPath, handlerFolder)
 		}
 	}
 
+	// fmt.Printf("Preparing: %s %s\n", handler+"/", functionPath)
+
 	if isRunningInCI() {
 		defaultDirPermissions = 0777
 	}
@@ -356,8 +333,8 @@ func CreateBuildContext(functionName string, handler string, template *stack.Lan
 		return tempPath, mkdirErr
 	}
 
-	if useTemplate {
-		if err := CopyFiles(path.Join("./template/", template.Language), tempPath); err != nil {
+	if useFunction {
+		if err := CopyFiles(path.Join("./template/", language), tempPath); err != nil {
 			fmt.Printf("Error copying template directory: %s.\n", err.Error())
 			return tempPath, err
 		}
@@ -373,7 +350,6 @@ func CreateBuildContext(functionName string, handler string, template *stack.Lan
 
 	for _, info := range infos {
 		switch info.Name() {
-		// Ignore the build and template folder.
 		case "build", "template":
 			fmt.Printf("Skipping \"%s\" folder\n", info.Name())
 			continue
@@ -392,12 +368,16 @@ func CreateBuildContext(functionName string, handler string, template *stack.Lan
 		if err != nil {
 			return tempPath, err
 		}
-
-		// Note that if template is nil, i.e. is a `dockerfile` template, then
+		// Note that if useFunction is false, ie is a `dockerfile` template, then
 		// functionPath == tempPath, the docker build context, not the `function` handler folder
 		// inside the docker build context
-		if err := CopyFiles(extraPathAbs, filepath.Clean(path.Join(functionPath, extraPath))); err != nil {
-			return tempPath, err
+		copyErr := CopyFiles(
+			extraPathAbs,
+			filepath.Clean(path.Join(functionPath, extraPath)),
+		)
+
+		if copyErr != nil {
+			return tempPath, copyErr
 		}
 	}
 
@@ -536,6 +516,6 @@ func deDuplicate(buildOptPackages []string) []string {
 	return retPackages
 }
 
-func isDockerfileTemplate(language string) bool {
-	return strings.ToLower(language) == "dockerfile"
+func isLanguageTemplate(language string) bool {
+	return strings.ToLower(language) != "dockerfile"
 }
diff --git a/builder/publish.go b/builder/publish.go
index d6b29ad1..6bca112c 100644
--- a/builder/publish.go
+++ b/builder/publish.go
@@ -61,18 +61,7 @@ func PublishImage(image string, handler string, functionName string, language st
 			return fmt.Errorf("building %s, %s is an invalid path", functionName, handler)
 		}
 
-		// To avoid breaking the CLI for custom templates that do not set the language attribute
-		// we ensure it is always set.
-		//
-		// While templates are expected to have the language in `template.yaml` set to the same name as the template folder
-		// this was never enforced.
-		langTemplate.Language = language
-
-		if isDockerfileTemplate(langTemplate.Language) {
-			langTemplate = nil
-		}
-
-		tempPath, err := CreateBuildContext(functionName, handler, langTemplate, copyExtraPaths)
+		tempPath, err := createBuildContext(functionName, handler, language, isLanguageTemplate(language), langTemplate.HandlerFolder, copyExtraPaths)
 		if err != nil {
 			return err
 		}


Test patch:
diff --git a/builder/build_test.go b/builder/build_test.go
index 61d0528c..d740c9b1 100644
--- a/builder/build_test.go
+++ b/builder/build_test.go
@@ -9,22 +9,23 @@ import (
 	"github.com/openfaas/faas-cli/stack"
 )
 
-func Test_isDockerfileTemplate_Dockerfile(t *testing.T) {
+func Test_isLanguageTemplate_Dockerfile(t *testing.T) {
 
 	language := "Dockerfile"
 
-	want := true
-	got := isDockerfileTemplate(language)
+	want := false
+	got := isLanguageTemplate(language)
 	if got != want {
 		t.Errorf("language: %s got %v, want %v", language, got, want)
 	}
 }
 
-func Test_isDockerfileTemplate_Node(t *testing.T) {
+func Test_isLanguageTemplate_Node(t *testing.T) {
+
 	language := "node"
 
-	want := false
-	got := isDockerfileTemplate(language)
+	want := true
+	got := isLanguageTemplate(language)
 	if got != want {
 		t.Errorf("language: %s got %v, want %v", language, got, want)
 	}