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
2 changes: 1 addition & 1 deletion cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
39 changes: 33 additions & 6 deletions cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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()
Expand All @@ -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")

Expand All @@ -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.
Expand All @@ -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:"-"`
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()
}
5 changes: 5 additions & 0 deletions pkg/functions/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
50 changes: 42 additions & 8 deletions pkg/scaffolding/middleware_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,52 @@ 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)
}

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
Expand Down Expand Up @@ -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(`<quarkus\.platform\.version>(.*?)</quarkus\.platform\.version>`)
return pomDetector.detect(fs, pomXmlPath, re)
}

type springMiddlewareVersionDetector struct{}
Expand All @@ -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(`<spring-cloud\.version>(.*?)</spring-cloud\.version>`)
return pomDetector.detect(fs, pomXmlPath, re)
}

type rustMiddlewareVersionDetector struct{}
Expand Down Expand Up @@ -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)
Expand All @@ -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(`<quarkus\.platform\.version>(.*?)</quarkus\.platform\.version>`)
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)
}
96 changes: 96 additions & 0 deletions pkg/scaffolding/middleware_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: `
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.8</version>
<relativePath/>
</parent>
<groupId>com.example.events</groupId>
<artifactId>function</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring Cloud Function::Http Example</name>
<description>A Spring Cloud Function, Http Example</description>
<properties>
<java.version>21</java.version>
<spring-cloud.version>2025.0.0</spring-cloud.version>
<compiler-plugin.version>3.11.0</compiler-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
</dependencies>
</dependencyManagement>
</project>
`,
Version: "2025.0.0",
WantErr: false,
}, {
Name: "Version not found",
PomXml: `
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.8</version>
<relativePath/>
</parent>
<groupId>com.example.events</groupId>
<artifactId>function</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring Cloud Function::Http Example</name>
<description>A Spring Cloud Function, Http Example</description>
<properties>
<java.version>21</java.version>
<compiler-plugin.version>3.11.0</compiler-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
</dependencies>
</dependencyManagement>
</project>
`,
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)
}
})
}
}
Loading