From 5ad09e8f9064e48deb57b8dba6c84c13104e4e0d Mon Sep 17 00:00:00 2001 From: Abhijeet Dey Date: Fri, 24 Oct 2025 10:28:32 +0530 Subject: [PATCH 1/2] Added Support mapping of group field in CRDs Signed-off-by: Abhijeet Dey --- generators/artifacthub/package.go | 14 ++++++++++++-- generators/github/package.go | 16 +++++++++++++-- utils/component/generator.go | 29 ++++++++++++++++++++++++++++ utils/component/generator_test.go | 29 ++++++++++++++++++++++++++++ utils/component/openapi_generator.go | 11 +++++++++-- 5 files changed, 93 insertions(+), 6 deletions(-) diff --git a/generators/artifacthub/package.go b/generators/artifacthub/package.go index b24414fb..5422cddc 100644 --- a/generators/artifacthub/package.go +++ b/generators/artifacthub/package.go @@ -76,11 +76,21 @@ func (pkg AhPackage) GenerateComponents(group string) ([]_component.ComponentDef } comp.Model.Metadata.AdditionalProperties["source_uri"] = pkg.ChartUrl comp.Model.Version = pkg.Version - comp.Model.Name = pkg.Name + + // Derive model from the CRD's API group when available; otherwise, fallback to package name + group := component.ExtractGroupFromAPIVersion(comp.Component.Version) + modelName, displayName := component.GroupToModel(group, pkg.Name) + if strings.TrimSpace(modelName) == "" { + modelName = pkg.Name + } + if strings.TrimSpace(displayName) == "" { + displayName = manifests.FormatToReadableString(modelName) + } + comp.Model.Name = modelName comp.Model.Category = category.CategoryDefinition{ Name: "", } - comp.Model.DisplayName = manifests.FormatToReadableString(comp.Model.Name) + comp.Model.DisplayName = displayName components = append(components, comp) } return components, nil diff --git a/generators/github/package.go b/generators/github/package.go index fc4e09ad..7229dd5a 100644 --- a/generators/github/package.go +++ b/generators/github/package.go @@ -3,6 +3,7 @@ package github import ( "bytes" "os" + "strings" "github.com/meshery/meshkit/utils" "github.com/meshery/meshkit/utils/component" @@ -81,11 +82,22 @@ func (gp GitHubPackage) GenerateComponents(group string) ([]_component.Component comp.Model.Metadata.AdditionalProperties["source_uri"] = gp.SourceURL comp.Model.Version = gp.version - comp.Model.Name = gp.Name + + // Derive model from the CRD's API group when available; otherwise, fallback to package name + group := component.ExtractGroupFromAPIVersion(comp.Component.Version) + modelName, displayName := component.GroupToModel(group, gp.Name) + // Avoid empty names + if strings.TrimSpace(modelName) == "" { + modelName = gp.Name + } + if strings.TrimSpace(displayName) == "" { + displayName = manifests.FormatToReadableString(modelName) + } + comp.Model.Name = modelName comp.Model.Category = category.CategoryDefinition{ Name: "", } - comp.Model.DisplayName = manifests.FormatToReadableString(comp.Model.Name) + comp.Model.DisplayName = displayName components = append(components, comp) } diff --git a/utils/component/generator.go b/utils/component/generator.go index fa40f582..3bc8eb37 100644 --- a/utils/component/generator.go +++ b/utils/component/generator.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" "cuelang.org/go/cue" "github.com/meshery/meshkit/utils" @@ -58,6 +59,34 @@ var OpenAPISpecPathConfig = CuePathConfig{ var Configs = []CuePathConfig{DefaultPathConfig, DefaultPathConfig2} +// GroupToModel determines the model name and display name from a CRD/OpenAPI group value. +// - If group is non-empty, model name is the exact group (e.g., "monitor.azure.com"). +// Display name is a title-cased, dot-separated host converted to words (e.g., "Monitor Azure Com"). +// - If group is empty, fallbackName and its formatted variant are used. +func GroupToModel(group, fallbackName string) (modelName, displayName string) { + if strings.TrimSpace(group) != "" { + // Title case each dot-separated part and join with space for display name + parts := strings.Split(group, ".") + for i := range parts { + if parts[i] == "" { + continue + } + parts[i] = strings.ToUpper(parts[i][:1]) + strings.ToLower(parts[i][1:]) + } + return group, strings.Join(parts, " ") + } + return fallbackName, manifests.FormatToReadableString(fallbackName) +} + +// ExtractGroupFromAPIVersion returns the API group from a k8s apiVersion string like "group/version". +// If apiVersion does not contain '/', it returns an empty string. +func ExtractGroupFromAPIVersion(apiVersion string) string { + if strings.Contains(apiVersion, "/") { + return strings.SplitN(apiVersion, "/", 2)[0] + } + return "" +} + func IncludeComponentBasedOnGroup(resource string, groupFilter string) (bool, error) { if groupFilter == "" { return true, nil diff --git a/utils/component/generator_test.go b/utils/component/generator_test.go index fb66d0be..23abda0b 100644 --- a/utils/component/generator_test.go +++ b/utils/component/generator_test.go @@ -155,3 +155,32 @@ func TestGenerate(t *testing.T) { }) } } + +func TestGroupToModel_WithGroup(t *testing.T) { + name, display := GroupToModel("monitor.azure.com", "fallback") + if name != "monitor.azure.com" { + t.Fatalf("expected model name 'monitor.azure.com', got '%s'", name) + } + if display != "Monitor Azure Com" { + t.Fatalf("expected display name 'Monitor Azure Com', got '%s'", display) + } +} + +func TestGroupToModel_EmptyGroup(t *testing.T) { + name, display := GroupToModel("", "fooBarBaz") + if name != "fooBarBaz" { + t.Fatalf("expected fallback model name 'fooBarBaz', got '%s'", name) + } + if display != "Foo Bar Baz" { + t.Fatalf("expected display name 'Foo Bar Baz', got '%s'", display) + } +} + +func TestExtractGroupFromAPIVersion(t *testing.T) { + if g := ExtractGroupFromAPIVersion("apps/v1"); g != "apps" { + t.Fatalf("expected 'apps', got '%s'", g) + } + if g := ExtractGroupFromAPIVersion("v1"); g != "" { + t.Fatalf("expected '', got '%s'", g) + } +} diff --git a/utils/component/openapi_generator.go b/utils/component/openapi_generator.go index c40a3961..7c94efa9 100644 --- a/utils/component/openapi_generator.go +++ b/utils/component/openapi_generator.go @@ -121,6 +121,13 @@ func GenerateFromOpenAPI(resource string, pkg models.Package) ([]component.Compo } } + // Decide model name/display based on group when present + modelName := pkg.GetName() + displayModelName := manifests.FormatToReadableString(pkg.GetName()) + if g, _ := groupCue.String(); g != "" { + modelName, displayModelName = GroupToModel(g, pkg.GetName()) + } + c := component.ComponentDefinition{ SchemaVersion: v1beta1.ComponentSchemaVersion, Format: component.JSON, @@ -138,8 +145,8 @@ func GenerateFromOpenAPI(resource string, pkg models.Package) ([]component.Compo Model: model.Model{ Version: pkg.GetVersion(), }, - Name: pkg.GetName(), - DisplayName: manifests.FormatToReadableString(pkg.GetName()), + Name: modelName, + DisplayName: displayModelName, Metadata: &model.ModelDefinition_Metadata{ AdditionalProperties: map[string]interface{}{ "source_uri": pkg.GetSourceURL(), From 35c9e285303db189faf7a4d3f602144a2d451d45 Mon Sep 17 00:00:00 2001 From: Abhijeet Dey Date: Sun, 26 Oct 2025 11:41:11 +0530 Subject: [PATCH 2/2] Codecov test case fixing Signed-off-by: Abhijeet Dey --- utils/component/generator.go | 7 ++++- utils/component/generator_test.go | 48 ++++++++++++++++++++----------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/utils/component/generator.go b/utils/component/generator.go index 3bc8eb37..5dcfce7e 100644 --- a/utils/component/generator.go +++ b/utils/component/generator.go @@ -75,7 +75,12 @@ func GroupToModel(group, fallbackName string) (modelName, displayName string) { } return group, strings.Join(parts, " ") } - return fallbackName, manifests.FormatToReadableString(fallbackName) + // Fallback: format and ensure leading character is capitalized for display + display := manifests.FormatToReadableString(fallbackName) + if display != "" { + display = strings.ToUpper(display[:1]) + display[1:] + } + return fallbackName, display } // ExtractGroupFromAPIVersion returns the API group from a k8s apiVersion string like "group/version". diff --git a/utils/component/generator_test.go b/utils/component/generator_test.go index 23abda0b..ffeec8dd 100644 --- a/utils/component/generator_test.go +++ b/utils/component/generator_test.go @@ -156,23 +156,39 @@ func TestGenerate(t *testing.T) { } } -func TestGroupToModel_WithGroup(t *testing.T) { - name, display := GroupToModel("monitor.azure.com", "fallback") - if name != "monitor.azure.com" { - t.Fatalf("expected model name 'monitor.azure.com', got '%s'", name) - } - if display != "Monitor Azure Com" { - t.Fatalf("expected display name 'Monitor Azure Com', got '%s'", display) - } -} - -func TestGroupToModel_EmptyGroup(t *testing.T) { - name, display := GroupToModel("", "fooBarBaz") - if name != "fooBarBaz" { - t.Fatalf("expected fallback model name 'fooBarBaz', got '%s'", name) +func TestGroupToModel(t *testing.T) { + cases := []struct { + name string + group string + fallback string + expectName string + expectDisplay string + }{ + { + name: "with group", + group: "monitor.azure.com", + fallback: "ignored", + expectName: "monitor.azure.com", + expectDisplay: "Monitor Azure Com", + }, + { + name: "empty group uses fallback", + group: "", + fallback: "fooBarBaz", + expectName: "fooBarBaz", + expectDisplay: "Foo Bar Baz", + }, } - if display != "Foo Bar Baz" { - t.Fatalf("expected display name 'Foo Bar Baz', got '%s'", display) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + name, display := GroupToModel(tc.group, tc.fallback) + if name != tc.expectName { + t.Fatalf("expected model name '%s', got '%s'", tc.expectName, name) + } + if display != tc.expectDisplay { + t.Fatalf("expected display name '%s', got '%s'", tc.expectDisplay, display) + } + }) } }