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) + } + }) + } +}