diff --git a/cmd/root_test.go b/cmd/root_test.go
index a4454a8cb6..555e1e6a83 100644
--- a/cmd/root_test.go
+++ b/cmd/root_test.go
@@ -171,7 +171,7 @@ func TestVerbose(t *testing.T) {
name: "verbose as version's flag",
args: []string{"version", "-v"},
want: "Version: v0.42.0",
- wantLF: 5,
+ wantLF: 24,
},
{
name: "no verbose",
diff --git a/cmd/version.go b/cmd/version.go
index c8e94f5711..e909a21ebb 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -10,8 +10,8 @@ import (
"github.com/ory/viper"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
-
"knative.dev/func/pkg/config"
+ fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/k8s"
)
@@ -47,8 +47,8 @@ DESCRIPTION
`,
SuggestFor: []string{"vers", "version"}, //nolint:misspell
PreRunE: bindEnv("verbose", "output"),
- Run: func(cmd *cobra.Command, _ []string) {
- runVersion(cmd, version)
+ RunE: func(cmd *cobra.Command, _ []string) error {
+ return runVersion(cmd, version)
},
}
cfg, err := config.NewDefault()
@@ -69,7 +69,7 @@ DESCRIPTION
}
// Run
-func runVersion(cmd *cobra.Command, v Version) {
+func runVersion(cmd *cobra.Command, v Version) error {
verbose := viper.GetBool("verbose")
output := viper.GetString("output")
@@ -88,7 +88,14 @@ func runVersion(cmd *cobra.Command, v Version) {
v.SocatImage = k8s.SocatImage
v.TarImage = k8s.TarImage
+ latestMW, err := fn.LatestMiddlewareVersions()
+ if err != nil {
+ return fmt.Errorf("error fetching latest middleware versions: %w", err)
+ }
+ v.MiddlewareVersions = latestMW
+
write(cmd.OutOrStdout(), v, output)
+ return nil
}
// Version information populated on build.
@@ -107,6 +114,10 @@ type Version struct {
SocatImage string `json:"socatImage,omitempty" yaml:"socatImage,omitempty"`
// TarImage is the tar image used by the function.
TarImage string `json:"tarImage,omitempty" yaml:"tarImage,omitempty"`
+ // MiddlewareVersions provides information about the latest middleware version
+ // for a given platform and invokeType
+ MiddlewareVersions MiddlewareVersions `json:"middlewareVersions,omitempty" yaml:"middlewareVersions,omitempty"`
+
// Verbose printing enabled for the string representation.
Verbose bool `json:"-" yaml:"-"`
}
@@ -135,12 +146,14 @@ func (v Version) StringVerbose() string {
"Knative: %s\n"+
"Commit: %s\n"+
"SocatImage: %s\n"+
- "TarImage: %s\n",
+ "TarImage: %s\n"+
+ "Middleware Versions: \n%s",
vers,
kver,
hash,
v.SocatImage,
- v.TarImage)
+ v.TarImage,
+ v.MiddlewareVersions)
}
// Human prints version information in human-readable format.
@@ -176,3 +189,17 @@ func (v Version) YAML(w io.Writer) error {
func (v Version) URL(w io.Writer) error {
return fmt.Errorf("URL format not supported for version command")
}
+
+type MiddlewareVersions map[string]map[string]string
+
+func (mv MiddlewareVersions) String() string {
+ sb := strings.Builder{}
+ for platform, pInfo := range mv {
+ sb.WriteString(" " + platform + ":\n")
+ for invokeType, version := range pInfo {
+ sb.WriteString(" " + invokeType + ": " + version + "\n")
+ }
+ }
+
+ return sb.String()
+}
diff --git a/pkg/functions/middleware.go b/pkg/functions/middleware.go
index 11d88100ca..4d13799175 100644
--- a/pkg/functions/middleware.go
+++ b/pkg/functions/middleware.go
@@ -3,6 +3,7 @@ package functions
import (
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
+ "knative.dev/func/pkg/scaffolding"
)
// MiddlewareVersion gets the used middleware version of a function image.
@@ -35,3 +36,7 @@ func MiddlewareVersion(image string) (string, error) {
return cfg.Config.Labels[MiddlewareVersionLabelKey], nil
}
+
+func LatestMiddlewareVersions() (map[string]map[string]string, error) {
+ return scaffolding.MiddlewareVersions(EmbeddedTemplatesFS)
+}
diff --git a/pkg/scaffolding/middleware_version.go b/pkg/scaffolding/middleware_version.go
index 68f94b99aa..ccd75068ab 100644
--- a/pkg/scaffolding/middleware_version.go
+++ b/pkg/scaffolding/middleware_version.go
@@ -24,7 +24,7 @@ func MiddlewareVersion(src, runtime, invoke string, fs filesystem.Filesystem) (s
return "", fmt.Errorf("failed to detect signature: %w", err)
}
- vd, err := getVersionDetector(runtime)
+ vd, err := getMiddlewareVersionDetector(runtime)
if err != nil {
return "", fmt.Errorf("failed to get middleware version detector: %w", err)
}
@@ -32,11 +32,44 @@ func MiddlewareVersion(src, runtime, invoke string, fs filesystem.Filesystem) (s
return vd.Detect(fs, s)
}
+// MiddlewareVersions returns the middleware versions for all the runtimes and invoke types
+// for the given filesystem (which must contain the scaffolding at '[runtime]/scaffolding')
+func MiddlewareVersions(fs filesystem.Filesystem) (map[string]map[string]string, error) {
+ latest := make(map[string]map[string]string)
+
+ runtimes := []string{"go", "python", "node", "typescript", "quarkus", "java"}
+ invokeTypes := []string{"http", "cloudevent"}
+
+ for _, runtime := range runtimes {
+ for _, invoke := range invokeTypes {
+ sig := toSignature(true, invoke)
+
+ vd, err := getMiddlewareVersionDetector(runtime)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get middleware version detector: %w", err)
+ }
+
+ latestVersion, err := vd.Detect(fs, sig)
+ if err != nil {
+ return nil, fmt.Errorf("failed to detect latest middleware version: %w", err)
+ }
+
+ if latest[runtime] == nil {
+ latest[runtime] = make(map[string]string)
+ }
+
+ latest[runtime][invoke] = latestVersion
+ }
+ }
+
+ return latest, nil
+}
+
type middlewareVersionDetector interface {
Detect(fs filesystem.Filesystem, sig Signature) (string, error)
}
-func getVersionDetector(runtime string) (middlewareVersionDetector, error) {
+func getMiddlewareVersionDetector(runtime string) (middlewareVersionDetector, error) {
switch runtime {
case "go":
return &golangMiddlewareVersionDetector{}, nil
@@ -171,7 +204,8 @@ func (d *quarkusMiddlewareVersionDetector) Detect(fs filesystem.Filesystem, sig
pomXmlPath := fmt.Sprintf("quarkus/%s/pom.xml", invoke)
pomDetector := &pomMiddlewareVersionDetector{}
- return pomDetector.detect(fs, sig, pomXmlPath)
+ re := regexp.MustCompile(`(.*?)`)
+ return pomDetector.detect(fs, pomXmlPath, re)
}
type springMiddlewareVersionDetector struct{}
@@ -184,7 +218,8 @@ func (d *springMiddlewareVersionDetector) Detect(fs filesystem.Filesystem, sig S
pomXmlPath := fmt.Sprintf("springboot/%s/pom.xml", invoke)
pomDetector := &pomMiddlewareVersionDetector{}
- return pomDetector.detect(fs, sig, pomXmlPath)
+ re := regexp.MustCompile(`(.*?)`)
+ return pomDetector.detect(fs, pomXmlPath, re)
}
type rustMiddlewareVersionDetector struct{}
@@ -227,7 +262,7 @@ func (d *packageJsonMiddlewareVersionDetector) detect(fs filesystem.Filesystem,
type pomMiddlewareVersionDetector struct{}
-func (d *pomMiddlewareVersionDetector) detect(fs filesystem.Filesystem, sig Signature, pomXmlPath string) (string, error) {
+func (d *pomMiddlewareVersionDetector) detect(fs filesystem.Filesystem, pomXmlPath string, dependencyPropertyPattern *regexp.Regexp) (string, error) {
pomXml, err := fs.Open(pomXmlPath)
if err != nil {
return "", fmt.Errorf("failed to open pom.xml: %w", err)
@@ -239,11 +274,10 @@ func (d *pomMiddlewareVersionDetector) detect(fs filesystem.Filesystem, sig Sign
return "", fmt.Errorf("failed to read pom.xml: %w", err)
}
- re := regexp.MustCompile(`(.*?)`)
- match := re.FindSubmatch(content)
+ match := dependencyPropertyPattern.FindSubmatch(content)
if len(match) == 2 {
return string(match[1]), nil
}
- return "", fmt.Errorf("quarkus.platform.version property not found in %s", pomXmlPath)
+ return "", fmt.Errorf("dependency property not found in %s", pomXmlPath)
}
diff --git a/pkg/scaffolding/middleware_version_test.go b/pkg/scaffolding/middleware_version_test.go
index 61071130df..4f3af2a503 100644
--- a/pkg/scaffolding/middleware_version_test.go
+++ b/pkg/scaffolding/middleware_version_test.go
@@ -383,3 +383,99 @@ func TestMiddlewareVersionDetector_Quarkus(t *testing.T) {
})
}
}
+
+func TestMiddlewareVersionDetector_Java(t *testing.T) {
+ tests := []struct {
+ Name string // Name of the test
+ PomXml string // pom.xml file
+ Version string // Version Expected
+ WantErr bool // Error Expected
+ }{
+ {
+ Name: "Version exists",
+ PomXml: `
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.5.8
+
+
+ com.example.events
+ function
+ 0.0.1-SNAPSHOT
+ Spring Cloud Function::Http Example
+ A Spring Cloud Function, Http Example
+
+ 21
+ 2025.0.0
+ 3.11.0
+
+
+
+
+
+
+ `,
+ Version: "2025.0.0",
+ WantErr: false,
+ }, {
+ Name: "Version not found",
+ PomXml: `
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.5.8
+
+
+ com.example.events
+ function
+ 0.0.1-SNAPSHOT
+ Spring Cloud Function::Http Example
+ A Spring Cloud Function, Http Example
+
+ 21
+ 3.11.0
+
+
+
+
+
+
+ `,
+ WantErr: true,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.Name, func(t *testing.T) {
+
+ root, cleanup := Mktemp(t)
+ defer cleanup()
+
+ pomDir := filepath.Join(root, "springboot", "http")
+ if err := os.MkdirAll(pomDir, os.ModePerm); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(filepath.Join(pomDir, "pom.xml"), []byte(test.PomXml), os.ModePerm); err != nil {
+ t.Fatal(err)
+ }
+
+ d := &springMiddlewareVersionDetector{}
+ fs := filesystem.NewOsFilesystem(root)
+ v, err := d.Detect(fs, InstancedHTTP)
+ if (err != nil) != test.WantErr {
+ t.Fatalf("got error %v, want error %v", err, test.WantErr)
+ }
+
+ if test.Version != v {
+ t.Errorf("got version %s, want %s", test.Version, v)
+ }
+ })
+ }
+}