Repository: knative-sandbox/kn-plugin-func
Pull request #1160 (2022-08-10 15:20:30)

Added binary builds for platforms ppc64le and s390x
# Changes

This PR adds the binaries for platforms ppc64le and s390x to the list of release artifacts. Although at this point function support on these platforms is limited (thanks to paketo.io only supporting x86_64) this is the first step in properly suppporting functions on ppc64le and s390x (possibly via the s2i feature).

/kind enhancement


knative-prow[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative-sandbox/kn-plugin-func/pull/1160#" title="Author self-approved">haubenr</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign matejvasek for approval by writing `/assign @matejvasek` in a comment. For more information see:[The Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative-sandbox%2Fkn-plugin-func).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/knative-sandbox/kn-plugin-func/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["matejvasek"]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/1160?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) Report
> Merging [#1160](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/1160?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) (c94b039) into [main](https://codecov.io/gh/knative-sandbox/kn-plugin-func/commit/849c2cd7a187d37693ba963b6cd1fc9a0377ad4d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) (849c2cd) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##             main    #1160   +/-   ##
=======================================
  Coverage   46.09%   46.09%           
=======================================
  Files          66       66           
  Lines        8705     8705           
=======================================
  Hits         4013     4013           
  Misses       4306     4306           
  Partials      386      386           
```



Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox)

lance: /lgtm
/hold for tests
Thanks @haubenr!
lance: /unhold

Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index d136837c..260c2d41 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -54,6 +54,14 @@ jobs:
         with:
           name: Linux Binary (ARM)
           path: func_linux_arm64
+      - uses: actions/upload-artifact@v2
+        with:
+          name: Linux Binary (PPC64LE)
+          path: func_linux_ppc64le
+      - uses: actions/upload-artifact@v2
+        with:
+          name: Linux Binary (S390X)
+          path: func_linux_s390x
       - uses: actions/upload-artifact@v2
         with:
           name: Windows Binary
diff --git a/Makefile b/Makefile
index adc1b3bb..210d1049 100644
--- a/Makefile
+++ b/Makefile
@@ -10,6 +10,8 @@ BIN_DARWIN_AMD64   ?= $(BIN)_darwin_amd64
 BIN_DARWIN_ARM64   ?= $(BIN)_darwin_arm64
 BIN_LINUX_AMD64   ?= $(BIN)_linux_amd64
 BIN_LINUX_ARM64   ?= $(BIN)_linux_arm64
+BIN_LINUX_PPC64LE ?= $(BIN)_linux_ppc64le
+BIN_LINUX_S390X   ?= $(BIN)_linux_s390x
 BIN_WINDOWS ?= $(BIN)_windows_amd64.exe
 
 # Version
@@ -151,7 +153,7 @@ test-e2e-runtime: ## Run end-to-end lifecycle tests using an available cluster f
 ##@ Release Artifacts
 ######################
 
-cross-platform: darwin-arm64 darwin-amd64 linux-amd64 linux-arm64 windows ## Build all distributable (cross-platform) binaries
+cross-platform: darwin-arm64 darwin-amd64 linux-amd64 linux-arm64 linux-ppc64le linux-s390x windows ## Build all distributable (cross-platform) binaries
 
 darwin-arm64: $(BIN_DARWIN_ARM64) ## Build for mac M1
 
@@ -173,6 +175,16 @@ linux-arm64: $(BIN_LINUX_ARM64) ## Build for Linux arm64
 $(BIN_LINUX_ARM64): zz_filesystem_generated.go
 	env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o $(BIN_LINUX_ARM64) -ldflags $(LDFLAGS) ./cmd/$(BIN)
 
+linux-ppc64le: $(BIN_LINUX_PPC64LE) ## Build for Linux ppc64le
+
+$(BIN_LINUX_PPC64LE): zz_filesystem_generated.go
+	env CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le go build -o $(BIN_LINUX_PPC64LE) -ldflags $(LDFLAGS) ./cmd/$(BIN)
+
+linux-s390x: $(BIN_LINUX_S390X) ## Build for Linux s390x
+
+$(BIN_LINUX_S390X): zz_filesystem_generated.go
+	env CGO_ENABLED=0 GOOS=linux GOARCH=s390x go build -o $(BIN_LINUX_S390X) -ldflags $(LDFLAGS) ./cmd/$(BIN)
+
 windows: $(BIN_WINDOWS) ## Build for Windows
 
 $(BIN_WINDOWS): zz_filesystem_generated.go
diff --git a/cmd/languages.go b/cmd/languages.go
index 4af02926..f55c3da7 100644
--- a/cmd/languages.go
+++ b/cmd/languages.go
@@ -27,13 +27,13 @@ DESCRIPTION
 	This includes embedded (included) language runtimes as well as any installed
 	via the 'repositories add' command.
 
+	To specify a URI of a single, specific repository for which languages
+	should be displayed, use the --repository flag.
+
 	Installed repositories are by default located at ~/.func/repositories
 	($XDG_CONFIG_HOME/.func/repositories).  This can be overridden with
 	$FUNC_REPOSITORIES_PATH.
 
-	To specify a URI of a single, specific repository for which languages
-	should be displayed, use the --repository flag.
-
 	To see templates available for a given language, see the 'templates' command.
 
 
@@ -53,7 +53,7 @@ EXAMPLES
 		PreRunE: bindEnv("json", "repository"),
 	}
 
-	cmd.Flags().BoolP("json", "", false, "Set output to JSON format: $FUNC_JSON)")
+	cmd.Flags().BoolP("json", "", false, "Set output to JSON format. (Env: $FUNC_JSON)")
 	cmd.Flags().StringP("repository", "r", "", "URI to a specific repository to consider (Env: $FUNC_REPOSITORY)")
 
 	cmd.SetHelpFunc(defaultTemplatedHelp)
diff --git a/cmd/root.go b/cmd/root.go
index 9c4d9677..96513f20 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -99,6 +99,8 @@ EXAMPLES
 	cmd.AddCommand(NewCompletionCmd())
 	cmd.AddCommand(NewVersionCmd(config.Version))
 	cmd.AddCommand(NewLanguagesCmd(newClient))
+	cmd.AddCommand(NewTemplatesCmd(newClient))
+	cmd.AddCommand(NewLanguagesCmd(newClient))
 
 	// Help
 	// Overridden to process the help text as a template and have
diff --git a/cmd/templates.go b/cmd/templates.go
new file mode 100644
index 00000000..cca8ae49
--- /dev/null
+++ b/cmd/templates.go
@@ -0,0 +1,176 @@
+package cmd
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"os"
+	"strings"
+	"text/tabwriter"
+
+	"github.com/ory/viper"
+	"github.com/spf13/cobra"
+
+	fn "knative.dev/kn-plugin-func"
+)
+
+func NewTemplatesCmd(newClient ClientFactory) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "templates",
+		Short: "Templates",
+		Long: `
+NAME
+	{{.Name}} templates - list available templates
+
+SYNOPSIS
+	{{.Name}} templates [language] [--json] [-r|--repository]
+
+DESCRIPTION
+	List all templates available, optionally for a specific language runtime.
+
+	To specify a URI of a single, specific repository for which templates
+	should be displayed, use the --repository flag.
+	
+	Installed repositories are by default located at ~/.func/repositories
+	($XDG_CONFIG_HOME/.func/repositories).  This can be overridden with
+	$FUNC_REPOSITORIES_PATH.
+
+	To see all available language runtimes, see the 'languages' command.
+
+
+EXAMPLES
+
+	o Show a list of all available templates grouped by language runtime
+	  $ {{.Name}} templates
+
+	o Show a list of all templates for the Go runtime
+	  $ {{.Name}} templates go
+
+	o Return a list of all template runtimes in JSON output format
+	  $ {{.Name}} templates --json
+
+	o Return Go templates in a specific repository
+		$ {{.Name}} templates go --repository=https://github.com/boson-project/func-templates
+`,
+		SuggestFor: []string{"template", "templtaes", "templatse", "remplates",
+			"gemplates", "yemplates", "tenplates", "tekplates", "tejplates",
+			"temolates", "temllates", "temppates", "tempmates", "tempkates",
+			"templstes", "templztes", "templqtes", "templares", "templages", //nolint:misspell
+			"templayes", "templatee", "templatea", "templated", "templatew"},
+		PreRunE: bindEnv("json", "repository"),
+	}
+
+	cmd.Flags().BoolP("json", "", false, "Set output to JSON format. (Env: $FUNC_JSON)")
+	cmd.Flags().StringP("repository", "r", "", "URI to a specific repository to consider (Env: $FUNC_REPOSITORY)")
+
+	cmd.SetHelpFunc(defaultTemplatedHelp)
+
+	cmd.RunE = func(cmd *cobra.Command, args []string) error {
+		return runTemplates(cmd, args, newClient)
+	}
+
+	return cmd
+}
+
+func runTemplates(cmd *cobra.Command, args []string, newClient ClientFactory) (err error) {
+	// Gather config
+	cfg, err := newTemplatesConfig(newClient)
+	if err != nil {
+		return
+	}
+
+	// Client which will provide data
+	client, done := newClient(ClientConfig{Verbose: cfg.Verbose},
+		fn.WithRepository(cfg.Repository),             // Use exactly this repo OR
+		fn.WithRepositoriesPath(cfg.RepositoriesPath)) // Path on disk to installed repos
+	defer done()
+
+	// For a singl language runtime
+	// -------------------
+	if len(args) == 1 {
+		templates, err := client.Templates().List(args[0])
+		if err != nil {
+			return err
+		}
+		if cfg.JSON {
+			s, err := json.MarshalIndent(templates, "", "  ")
+			if err != nil {
+				return err
+			}
+			fmt.Fprintln(cmd.OutOrStdout(), string(s))
+		} else {
+			for _, template := range templates {
+				fmt.Fprintln(cmd.OutOrStdout(), template)
+			}
+		}
+		return nil
+	} else if len(args) > 1 {
+		return errors.New("unexpected extra arguments.")
+	}
+
+	// All language runtimes
+	// ------------
+	runtimes, err := client.Runtimes()
+	if err != nil {
+		return
+	}
+	if cfg.JSON {
+		// Gather into a single data structure for printing as json
+		templateMap := make(map[string][]string)
+		for _, runtime := range runtimes {
+			templates, err := client.Templates().List(runtime)
+			if err != nil {
+				return err
+			}
+			templateMap[runtime] = templates
+		}
+		s, err := json.MarshalIndent(templateMap, "", "  ")
+		if err != nil {
+			return err
+		}
+		fmt.Fprintln(cmd.OutOrStdout(), string(s))
+	} else {
+		// print using a formatted writer (sorted)
+		builder := strings.Builder{}
+		writer := tabwriter.NewWriter(&builder, 0, 0, 3, ' ', 0)
+		fmt.Fprint(writer, "LANGUAGE\tTEMPLATE\n")
+		for _, runtime := range runtimes {
+			templates, err := client.Templates().List(runtime)
+			if err != nil {
+				return err
+			}
+			for _, template := range templates {
+				fmt.Fprintf(writer, "%v\t%v\n", runtime, template)
+			}
+		}
+		writer.Flush()
+		fmt.Fprint(cmd.OutOrStdout(), builder.String())
+	}
+	return
+}
+
+type templatesConfig struct {
+	Verbose          bool
+	Repository       string // Consider only a specific repository (URI)
+	RepositoriesPath string // Override location on disk of "installed" repos
+	JSON             bool   // output as JSON
+}
+
+func newTemplatesConfig(newClient ClientFactory) (cfg templatesConfig, err error) {
+	// Repositories Path
+	// Not exposed as a flag due to potential confusion with the more likely
+	// "repository" flag, but still available as an environment variable
+	repositoriesPath := os.Getenv("FUNC_REPOSITORIES_PATH")
+	if repositoriesPath == "" { // if no env var provided
+		repositoriesPath = fn.New().RepositoriesPath() // default to ~/.config/func/repositories
+	}
+
+	cfg = templatesConfig{
+		Verbose:          viper.GetBool("verbose"),
+		Repository:       viper.GetString("repository"),
+		RepositoriesPath: repositoriesPath,
+		JSON:             viper.GetBool("json"),
+	}
+
+	return
+}
diff --git a/hack/release.sh b/hack/release.sh
index d559b8d7..e43425a1 100755
--- a/hack/release.sh
+++ b/hack/release.sh
@@ -22,10 +22,10 @@ VALIDATION_TESTS="make test"
 source $(dirname $0)/../vendor/knative.dev/hack/release.sh
 
 function build_release() {
-  echo "🚧 🐧 Building cross platform binaries: Linux 🐧 (amd64), MacOS 🍏, and Windows 🎠"
+  echo "🚧 🐧 Building cross platform binaries: Linux 🐧 (amd64 / arm64 / ppc64le / s390x), MacOS 🍏, and Windows 🎠"
   ETAG=${TAG} make cross-platform
 
-  ARTIFACTS_TO_PUBLISH="func_darwin_amd64 func_darwin_arm64 func_linux_amd64 func_linux_arm64 func_windows_amd64.exe"
+  ARTIFACTS_TO_PUBLISH="func_darwin_amd64 func_darwin_arm64 func_linux_amd64 func_linux_arm64 func_linux_ppc64le func_linux_s390x func_windows_amd64.exe"
   sha256sum ${ARTIFACTS_TO_PUBLISH} > checksums.txt
   ARTIFACTS_TO_PUBLISH="${ARTIFACTS_TO_PUBLISH} checksums.txt"
   echo "🧮     Checksum:"


Test patch:
diff --git a/cmd/templates_test.go b/cmd/templates_test.go
new file mode 100644
index 00000000..3ff10fa1
--- /dev/null
+++ b/cmd/templates_test.go
@@ -0,0 +1,142 @@
+package cmd
+
+import (
+	"testing"
+
+	fn "knative.dev/kn-plugin-func"
+	. "knative.dev/kn-plugin-func/testing"
+)
+
+// TestTemplates_Default ensures that the default behavior is listing all
+// templates for all language runtimes.
+func TestTemplates_Default(t *testing.T) {
+	defer WithEnvVar(t, "XDG_CONFIG_HOME", t.TempDir())() // ignore user-added
+	buf := piped(t)                                       // gather output
+	cmd := NewTemplatesCmd(NewClientFactory(func() *fn.Client {
+		return fn.New()
+	}))
+	if err := cmd.Execute(); err != nil {
+		t.Fatal(err)
+	}
+
+	expected := `LANGUAGE     TEMPLATE
+go           cloudevents
+go           http
+node         cloudevents
+node         http
+python       cloudevents
+python       http
+quarkus      cloudevents
+quarkus      http
+rust         cloudevents
+rust         http
+springboot   cloudevents
+springboot   http
+typescript   cloudevents
+typescript   http`
+	output := buf()
+	if output != expected {
+		t.Fatalf("expected:\n'%v'\n\ngot:\n'%v'\n", expected, output)
+	}
+}
+
+// TestTemplates_JSON ensures that listing templates respects the --json
+// output format.
+func TestTemplates_JSON(t *testing.T) {
+	defer WithEnvVar(t, "XDG_CONFIG_HOME", t.TempDir())() // ignore user-added
+	buf := piped(t)                                       // gather output
+	cmd := NewTemplatesCmd(NewClientFactory(func() *fn.Client {
+		return fn.New()
+	}))
+	cmd.SetArgs([]string{"--json"})
+	if err := cmd.Execute(); err != nil {
+		t.Fatal(err)
+	}
+
+	expected := `{
+  "go": [
+    "cloudevents",
+    "http"
+  ],
+  "node": [
+    "cloudevents",
+    "http"
+  ],
+  "python": [
+    "cloudevents",
+    "http"
+  ],
+  "quarkus": [
+    "cloudevents",
+    "http"
+  ],
+  "rust": [
+    "cloudevents",
+    "http"
+  ],
+  "springboot": [
+    "cloudevents",
+    "http"
+  ],
+  "typescript": [
+    "cloudevents",
+    "http"
+  ]
+}`
+
+	output := buf()
+	for i, c := range expected {
+		if len(output) <= i {
+			t.Fatalf("output missing character(s) '%v', '%s' and later\n", i, string(c))
+		}
+		if rune(output[i]) != c {
+			t.Fatalf("Character at index %v expected '%s', got '%s'\n", i, string(c), string(output[i]))
+		}
+	}
+
+	if output != expected {
+		t.Fatalf("expected:\n%v\ngot:\n%v\n", expected, output)
+	}
+}
+
+// TestTemplates_ByLanguage ensures that the output is correctly filtered
+// by language runtime when provided.
+func TestTemplates_ByLanguage(t *testing.T) {
+	defer WithEnvVar(t, "XDG_CONFIG_HOME", t.TempDir())() // ignore user-added
+	cmd := NewTemplatesCmd(NewClientFactory(func() *fn.Client {
+		return fn.New()
+	}))
+
+	// Test plain text
+	buf := piped(t)
+	cmd.SetArgs([]string{"go"})
+	if err := cmd.Execute(); err != nil {
+		t.Fatal(err)
+	}
+
+	expected := `cloudevents
+http`
+
+	output := buf()
+	if output != expected {
+		t.Fatalf("expected plain text:\n'%v'\ngot:\n'%v'\n", expected, output)
+	}
+
+	// Test JSON output
+	buf = piped(t)
+	cmd.SetArgs([]string{"go", "--json"})
+	if err := cmd.Execute(); err != nil {
+		t.Fatal(err)
+	}
+
+	expected = `[
+  "cloudevents",
+  "http"
+]`
+
+	output = buf()
+	if output != expected {
+		t.Fatalf("expected JSON:\n'%v'\ngot:\n'%v'\n", expected, output)
+	}
+
+}