Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Java dependencies with as many Red Hat built ones #262

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
30 changes: 30 additions & 0 deletions cmd/maven-redhat/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"flag"
"log"

"github.com/openshift-knative/hack/pkg/maven"
)

const (
pomFileFlag = "path"
)

func main() {
metadata, err := maven.ScrapRedHatMavenRegistry(maven.RedHatMavenGA)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's only Quarkus version, we might be good with:

➜  ~ curl -s "https://code.quarkus.redhat.com/api/platforms" | jq -r '.platforms[0].streams[0].releases[0].version'
3.8.5.SP1-redhat-00001

Copy link
Contributor

@dsimansk dsimansk Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SP1 is actually newer than 3.8.5.redhat-00003. Where regular 3.8.5 is standard release. Then SP1 stands for security patch 1 of 3.8.5 I believe.

https://maven.repository.redhat.com/ga/com/redhat/quarkus/platform/quarkus-bom/3.8.5.SP1-redhat-00001/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SP == service patch, sorry :)

Copy link
Member Author

@pierDipi pierDipi Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's only Quarkus version, we might be good with:

no, it's not only for Quarkus, see the list in the diff in the PR body

SP1 is actually newer than 3.8.5.redhat-00003. Where regular 3.8.5 is standard release. Then SP1 stands for security patch 1 of 3.8.5 I believe.

hmm, aren't versions here sorted ? https://maven.repository.redhat.com/ga/com/redhat/quarkus/platform/quarkus-bom/maven-metadata.xml

Also latest says 3.8.5.redhat-00003 and not the SP one

<latest>3.8.5.redhat-00003</latest>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are not unfortunately. The service patch actually might be fixing Quarkus platform libs etc, but core quarkus is not changing. Hence the prefix version stays the same, and the rest is incrementing very "naturally".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, ok, I think this is already better than what we currently have in Konflux builds, so special sorting can be added later or we can always add "bump dependencies" using other tools later (like renovate or dependabot)

if err != nil {
log.Fatal(err)
}

path := flag.String(pomFileFlag, "", "POM file path")
flag.Parse()

if path == nil || *path == "" {
log.Fatalf("--%s flag is required", pomFileFlag)
}

if err := maven.UpdatePomFile(metadata, maven.RedHatMavenGA, *path); err != nil {
log.Fatal(err)
}
}
2 changes: 2 additions & 0 deletions config/eventing-kafka-broker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,5 @@ repositories:
namespace: openshift
repo: eventing-kafka-broker
slackChannel: '#knative-eventing-ci'
pomFiles:
- data-plane/pom.xml
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/spf13/pflag v1.0.5
go.uber.org/zap v1.27.0
golang.org/x/mod v0.20.0
golang.org/x/net v0.25.0
golang.org/x/sync v0.7.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
Expand Down Expand Up @@ -110,7 +111,6 @@ require (
gocloud.dev v0.19.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
Expand Down
244 changes: 244 additions & 0 deletions pkg/maven/maven_pom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
package maven

import (
"encoding/xml"
"fmt"
"log"
"os"
"regexp"
"slices"
"strings"
)

// Pom represents the root structure of the pom.xml file
type Pom struct {
XMLName xml.Name `xml:"project"`
GroupID string `xml:"groupId"`
ArtifactID string `xml:"artifactId"`
Version string `xml:"version"`
Dependencies Dependencies `xml:"dependencies"`
DependencyManagement DependencyManagement `xml:"dependencyManagement"`
Build Build `xml:"build"`
Properties Properties `xml:"properties"`
}

// DependencyManagement represents the <dependencyManagement> block in the pom.xml
type DependencyManagement struct {
Dependencies Dependencies `xml:"dependencies"`
}

// Dependencies represents the <dependencies> block in the pom.xml
type Dependencies struct {
Dependency []Dependency `xml:"dependency"`
}

type Plugins struct {
Plugin []Dependency `xml:"plugin"`
}

type PluginManagement struct {
Plugins Plugins `xml:"plugins"`
}

type Build struct {
PluginManagement PluginManagement `xml:"pluginManagement"`
Plugins Plugins `xml:"plugins"`
}

// Dependency represents a single dependency block
type Dependency struct {
GroupID string `xml:"groupId"`
ArtifactID string `xml:"artifactId"`
Version string `xml:"version"`
}

// Properties represents the <properties> block in the pom.xml file
type Properties struct {
Entries map[string]string `xml:",any"`
}

func UpdatePomFile(metadata []Metadata, url string, path string) error {

pomFileBytes, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read file %q: %w", path, err)
}
p := &Pom{}
if err := xml.Unmarshal(pomFileBytes, p); err != nil {
return fmt.Errorf("failed to unmarshal pom file %q: %v", path, err)
}

metadataByGA := map[string]Metadata{}
for _, m := range metadata {
metadataByGA[depKey(m.GroupID, m.ArtifactID)] = m
}

pomFileStr := string(pomFileBytes)

if !strings.Contains(pomFileStr, url) {

repository := fmt.Sprintf(`
<repository>
<id>red-hat-ga</id>
<url>%s</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
`, url)

repositoriesRegex := regexp.MustCompile("(<repositories>.*)([\\s\\S]*<repository>[\\s\\S]+</repositories>.*)")
for _, match := range repositoriesRegex.FindAllStringSubmatch(pomFileStr, -1) {
pomFileStr = strings.ReplaceAll(
pomFileStr,
match[0],
fmt.Sprintf("%s%s%s", match[1], repository, match[2]),
)
}
}

for _, d := range p.DependencyManagement.Dependencies.Dependency {
pomFileStr, err = replaceDependency(metadataByGA, p, d, pomFileStr)
if err != nil {
return err
}
}
for _, d := range p.Dependencies.Dependency {
pomFileStr, err = replaceDependency(metadataByGA, p, d, pomFileStr)
if err != nil {
return err
}
}
for _, d := range p.Build.PluginManagement.Plugins.Plugin {
pomFileStr, err = replaceDependency(metadataByGA, p, d, pomFileStr)
if err != nil {
return err
}
}
for _, d := range p.Build.Plugins.Plugin {
pomFileStr, err = replaceDependency(metadataByGA, p, d, pomFileStr)
if err != nil {
return err
}
}

if err := os.WriteFile(path, []byte(pomFileStr), 0666); err != nil {
return fmt.Errorf("failed to update POM file %q: %w", path, err)
}
return nil
}

func replaceDependency(metadataByGA map[string]Metadata, p *Pom, d Dependency, pomFileStr string) (string, error) {

propVariable := regexp.MustCompile("\\$\\{(\\S+)}")
propValue := regexp.MustCompile("([0-9a-z._-]+)")

versionRegex := regexp.MustCompile("([0-9]+)\\.?([0-9]+)?\\.?([0-9]+)?")

groupId := d.GroupID
if d.GroupID == "io.quarkus" {
groupId = "com.redhat.quarkus.platform"
}
m, ok := metadataByGA[depKey(groupId, d.ArtifactID)]
if !ok {
return pomFileStr, nil
}
version := d.Version
propNameMatch := propVariable.FindStringSubmatch(d.Version)
if len(propNameMatch) > 1 {
version, ok = p.Properties.Entries[propNameMatch[1]]
if !ok {
return pomFileStr, fmt.Errorf("failed to replace variable %q from properties %#v", d.Version, p.Properties.Entries)
}
if match := propValue.FindStringSubmatch(version); len(match) > 1 {
version = match[1]
}
}

log.Printf("Dependency %q:%q:%q\n%#v\n", d.GroupID, d.ArtifactID, version, m)

versionPieces := versionRegex.FindStringSubmatch(version)
if len(versionPieces) < 2 {
log.Printf("Version %q is not parsable", version)
return pomFileStr, nil
}
log.Printf("Version %q, pieces %#v", version, versionPieces)

versions := make([]string, len(m.Versioning.Versions.Version))
copy(versions, m.Versioning.Versions.Version)
slices.Reverse(versions)

for _, v := range versions {
if v == version {
break
}

vPieces := versionRegex.FindStringSubmatch(v)
if len(vPieces) < 2 {
log.Printf("Version %q in metadata is not parsable (%s:%s)", version, m.GroupID, m.ArtifactID)
continue
}
log.Printf("Red Hat Version %q, pieces %#v", v, vPieces)

if vPieces[1] != versionPieces[1] {
// major version doesn't match
log.Printf("Version %q doesn't match major version in %q", version, v)
continue
}

if len(versionPieces) >= 3 && len(vPieces) >= 3 && vPieces[2] != versionPieces[2] {
// minor version doesn't match
log.Printf("Version %q doesn't match minor version in %q", version, v)
continue
}

rg := fmt.Sprintf("(.*<groupId>\\s*)%s(\\s*</groupId>\\s*<artifactId>\\s*)%s(\\s*</artifactId>\\s*<version>\\s*)%s(\\s*</version>.*)", regexp.QuoteMeta(d.GroupID), regexp.QuoteMeta(d.ArtifactID), regexp.QuoteMeta(d.Version))
log.Printf("Replacing %q:%q:%q with %q:%q:%q (regex %q)", d.GroupID, d.ArtifactID, version, m.GroupID, m.ArtifactID, v, rg)

artifactRegex := regexp.MustCompile(rg)
for _, match := range artifactRegex.FindAllStringSubmatch(pomFileStr, -1) {
oldM := fmt.Sprintf("%s%s%s%s%s%s%s", match[1], d.GroupID, match[2], d.ArtifactID, match[3], d.Version, match[4])
newM := fmt.Sprintf("%s%s%s%s%s%s%s", match[1], m.GroupID, match[2], m.ArtifactID, match[3], v, match[4])

log.Printf("match:\n%s\n%s\n", oldM, newM)

pomFileStr = strings.ReplaceAll(pomFileStr, oldM, newM)
}
}

return pomFileStr, nil
}

func depKey(groupId, artifactId string) string {
return fmt.Sprintf("%s:%s", groupId, artifactId)
}

// UnmarshalXML is a custom unmarshaler for handling dynamic XML elements in <properties>
func (p *Properties) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
p.Entries = make(map[string]string)
for {
// Read the next token
t, err := d.Token()
if err != nil {
return err
}
switch elem := t.(type) {
case xml.StartElement:
var value string
// Decode the value of the element
if err := d.DecodeElement(&value, &elem); err != nil {
return err
}
// Use the element name as the key
p.Entries[elem.Name.Local] = value
case xml.EndElement:
// Exit once we reach the end of the properties block
if elem.Name.Local == "properties" {
return nil
}
}
}
}
Loading
Loading