From db3f233aef5c6b21defc80010053ceadfc6fce16 Mon Sep 17 00:00:00 2001 From: Gabriele Santomaggio Date: Tue, 18 Feb 2025 15:06:04 +0100 Subject: [PATCH] Validate Version (#32) and product. Version should be >= 4.0 and product should be RabbitMQ. closes: https://github.com/rabbitmq/rabbitmq-amqp-go-client/issues/26 Signed-off-by: Gabriele Santomaggio --- pkg/rabbitmqamqp/amqp_connection.go | 31 +++++- pkg/rabbitmqamqp/amqp_connection_test.go | 2 + pkg/rabbitmqamqp/features_available.go | 98 +++++++++++++++++ pkg/rabbitmqamqp/features_available_test.go | 113 ++++++++++++++++++++ 4 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 pkg/rabbitmqamqp/features_available.go create mode 100644 pkg/rabbitmqamqp/features_available_test.go diff --git a/pkg/rabbitmqamqp/amqp_connection.go b/pkg/rabbitmqamqp/amqp_connection.go index c3ad61c..a3f1056 100644 --- a/pkg/rabbitmqamqp/amqp_connection.go +++ b/pkg/rabbitmqamqp/amqp_connection.go @@ -56,6 +56,9 @@ type AmqpConnOptions struct { } type AmqpConnection struct { + properties map[string]any + featuresAvailable *featuresAvailable + azureConnection *amqp.Conn id string management *AmqpManagement @@ -66,6 +69,11 @@ type AmqpConnection struct { entitiesTracker *entitiesTracker } +func (a *AmqpConnection) Properties() map[string]any { + return a.properties + +} + // NewPublisher creates a new Publisher that sends messages to the provided destination. // The destination is a TargetAddress that can be a Queue or an Exchange with a routing key. // See QueueAddress and ExchangeAddress for more information. @@ -129,10 +137,11 @@ func Dial(ctx context.Context, addresses []string, connOptions *AmqpConnOptions, // create the connection conn := &AmqpConnection{ - management: NewAmqpManagement(), - lifeCycle: NewLifeCycle(), - amqpConnOptions: connOptions, - entitiesTracker: newEntitiesTracker(), + management: NewAmqpManagement(), + lifeCycle: NewLifeCycle(), + amqpConnOptions: connOptions, + entitiesTracker: newEntitiesTracker(), + featuresAvailable: newFeaturesAvailable(), } tmp := make([]string, len(addresses)) copy(tmp, addresses) @@ -188,6 +197,20 @@ func (a *AmqpConnection) open(ctx context.Context, addresses []string, connOptio Error("Failed to open connection", ExtractWithoutPassword(addr), err) continue } + a.properties = azureConnection.Properties() + err = a.featuresAvailable.ParseProperties(a.properties) + if err != nil { + Warn("Validate properties Error.", ExtractWithoutPassword(addr), err) + } + + if !a.featuresAvailable.is4OrMore { + Warn("The server version is less than 4.0.0", ExtractWithoutPassword(addr)) + } + + if !a.featuresAvailable.isRabbitMQ { + Warn("The server is not RabbitMQ", ExtractWithoutPassword(addr)) + } + Debug("Connected to", ExtractWithoutPassword(addr)) break } diff --git a/pkg/rabbitmqamqp/amqp_connection_test.go b/pkg/rabbitmqamqp/amqp_connection_test.go index 7119001..5659bb7 100644 --- a/pkg/rabbitmqamqp/amqp_connection_test.go +++ b/pkg/rabbitmqamqp/amqp_connection_test.go @@ -24,6 +24,8 @@ var _ = Describe("AMQP connection Test", func() { SASLType: amqp.SASLTypePlain("guest", "guest")}) Expect(err).To(BeNil()) + Expect(connection.Properties()["product"]).To(Equal("RabbitMQ")) + err = connection.Close(context.Background()) Expect(err).To(BeNil()) }) diff --git a/pkg/rabbitmqamqp/features_available.go b/pkg/rabbitmqamqp/features_available.go new file mode 100644 index 0000000..ee92eea --- /dev/null +++ b/pkg/rabbitmqamqp/features_available.go @@ -0,0 +1,98 @@ +package rabbitmqamqp + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +type Version struct { + Major int + Minor int + Patch int +} + +func (v Version) Compare(other Version) int { + if v.Major != other.Major { + return v.Major - other.Major + } + if v.Minor != other.Minor { + return v.Minor - other.Minor + } + return v.Patch - other.Patch +} + +type featuresAvailable struct { + is4OrMore bool + is41OrMore bool + isRabbitMQ bool +} + +func newFeaturesAvailable() *featuresAvailable { + return &featuresAvailable{} +} + +func (f *featuresAvailable) ParseProperties(properties map[string]any) error { + if properties["version"] == nil { + return fmt.Errorf("missing version property") + } + + version := extractVersion(properties["version"].(string)) + if version == "" { + return fmt.Errorf("invalid version format: %s", version) + } + + f.is4OrMore = isVersionGreaterOrEqual(version, "4.0.0") + f.is41OrMore = isVersionGreaterOrEqual(version, "4.1.0") + f.isRabbitMQ = strings.EqualFold(properties["product"].(string), "RabbitMQ") + return nil +} + +func extractVersion(fullVersion string) string { + pattern := `(\d+\.\d+\.\d+)` + regex := regexp.MustCompile(pattern) + match := regex.FindStringSubmatch(fullVersion) + + if len(match) > 1 { + return match[1] + } + return "" +} + +func parseVersion(version string) (Version, error) { + parts := strings.Split(version, ".") + if len(parts) != 3 { + return Version{}, fmt.Errorf("invalid version format: %s", version) + } + + major, err := strconv.Atoi(parts[0]) + if err != nil { + return Version{}, fmt.Errorf("invalid major version: %s", parts[0]) + } + + minor, err := strconv.Atoi(parts[1]) + if err != nil { + return Version{}, fmt.Errorf("invalid minor version: %s", parts[1]) + } + + patch, err := strconv.Atoi(parts[2]) + if err != nil { + return Version{}, fmt.Errorf("invalid patch version: %s", parts[2]) + } + + return Version{Major: major, Minor: minor, Patch: patch}, nil +} + +func isVersionGreaterOrEqual(version, target string) bool { + v1, err := parseVersion(version) + if err != nil { + return false + } + + v2, err := parseVersion(target) + if err != nil { + return false + } + return v1.Compare(v2) >= 0 +} diff --git a/pkg/rabbitmqamqp/features_available_test.go b/pkg/rabbitmqamqp/features_available_test.go new file mode 100644 index 0000000..d45ec5c --- /dev/null +++ b/pkg/rabbitmqamqp/features_available_test.go @@ -0,0 +1,113 @@ +package rabbitmqamqp + +import ( + "fmt" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Available Features", func() { + + It("Parse Version", func() { + v, err := parseVersion("1.2.3") + Expect(err).NotTo(HaveOccurred()) + Expect(v).To(Equal(Version{Major: 1, Minor: 2, Patch: 3})) + + _, err = parseVersion("1.2") + Expect(err).To(HaveOccurred()) + Expect(fmt.Sprintf("%s", err)).To(ContainSubstring("invalid version format: 1.2")) + + _, err = parseVersion("error.3.3") + Expect(err).To(HaveOccurred()) + Expect(fmt.Sprintf("%s", err)).To(ContainSubstring("invalid major version: error")) + + _, err = parseVersion("1.error.3") + Expect(err).To(HaveOccurred()) + Expect(fmt.Sprintf("%s", err)).To(ContainSubstring("invalid minor version: error")) + + _, err = parseVersion("1.2.error") + Expect(err).To(HaveOccurred()) + Expect(fmt.Sprintf("%s", err)).To(ContainSubstring("invalid patch version: error")) + + v, err = parseVersion(extractVersion("3.12.1-rc1")) + Expect(err).NotTo(HaveOccurred()) + Expect(v).To(Equal(Version{Major: 3, Minor: 12, Patch: 1})) + + v, err = parseVersion(extractVersion("3.13.1-alpha.234")) + Expect(err).NotTo(HaveOccurred()) + Expect(v).To(Equal(Version{Major: 3, Minor: 13, Patch: 1})) + }) + + It("Is Version Greater Or Equal", func() { + Expect(isVersionGreaterOrEqual("1.2.3", "1.2.3")).To(BeTrue()) + Expect(isVersionGreaterOrEqual("1.2.3", "1.2.2")).To(BeTrue()) + Expect(isVersionGreaterOrEqual("1.2.3", "1.2.4")).To(BeFalse()) + Expect(isVersionGreaterOrEqual("1.2.3", "1.3.3")).To(BeFalse()) + Expect(isVersionGreaterOrEqual("1.2.3", "2.2.3")).To(BeFalse()) + Expect(isVersionGreaterOrEqual("3.1.3-alpha.1", "2.2.3")).To(BeFalse()) + Expect(isVersionGreaterOrEqual("3.3.3-rc.1", "2.2.3")).To(BeFalse()) + + Expect(isVersionGreaterOrEqual("error.3.2", "2.2.3")).To(BeFalse()) + Expect(isVersionGreaterOrEqual("4.3.2", "2.error.3")).To(BeFalse()) + + }) + + It("Available Features check Version", func() { + var availableFeatures = newFeaturesAvailable() + Expect(availableFeatures).NotTo(BeNil()) + Expect(availableFeatures.ParseProperties(map[string]any{})).NotTo(BeNil()) + + Expect(availableFeatures.ParseProperties(map[string]any{ + "version": "3.9.0", + "product": "RabbitMQ", + })).To(BeNil()) + Expect(availableFeatures.is4OrMore).To(BeFalse()) + Expect(availableFeatures.is41OrMore).To(BeFalse()) + Expect(availableFeatures.isRabbitMQ).To(BeTrue()) + + Expect(availableFeatures.ParseProperties(map[string]any{ + "version": "3.11.0", + "product": "RabbitMQ", + })).To(BeNil()) + Expect(availableFeatures.is4OrMore).To(BeFalse()) + Expect(availableFeatures.is41OrMore).To(BeFalse()) + Expect(availableFeatures.isRabbitMQ).To(BeTrue()) + + Expect(availableFeatures.ParseProperties(map[string]any{ + "version": "4.0.6-rc.1", + "product": "RabbitMQ", + })).To(BeNil()) + + Expect(availableFeatures.is4OrMore).To(BeTrue()) + Expect(availableFeatures.is41OrMore).To(BeFalse()) + Expect(availableFeatures.isRabbitMQ).To(BeTrue()) + + Expect(availableFeatures.ParseProperties(map[string]any{ + "version": "4.1.0", + "product": "RabbitMQ", + })).To(BeNil()) + + Expect(availableFeatures.is4OrMore).To(BeTrue()) + Expect(availableFeatures.is41OrMore).To(BeTrue()) + Expect(availableFeatures.isRabbitMQ).To(BeTrue()) + + Expect(availableFeatures.ParseProperties(map[string]any{ + "version": "4.1.0-beta.1", + "product": "Boh", + })).To(BeNil()) + + Expect(availableFeatures.is4OrMore).To(BeTrue()) + Expect(availableFeatures.is41OrMore).To(BeTrue()) + Expect(availableFeatures.isRabbitMQ).To(BeFalse()) + + Expect(availableFeatures.ParseProperties(map[string]any{ + "version": "4.1.0-rc.8", + "product": "rabbitmq", + })).To(BeNil()) + + Expect(availableFeatures.is4OrMore).To(BeTrue()) + Expect(availableFeatures.is41OrMore).To(BeTrue()) + Expect(availableFeatures.isRabbitMQ).To(BeTrue()) + + }) +})