diff --git a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/chart_converter.go b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/chart_converter.go index bedab3dccc8..5336aa59a7c 100644 --- a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/chart_converter.go +++ b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/chart_converter.go @@ -18,6 +18,7 @@ package kustomize import ( "fmt" + "os" "strconv" "strings" @@ -59,6 +60,16 @@ func (c *ChartConverter) WriteChartFiles(fs machinery.Filesystem) error { // Organize resources by their logical function resourceGroups := c.organizer.OrganizeByFunction() + // WARNING for unhandled resources + if extras, ok := resourceGroups["extras"]; ok && len(extras) > 0 { + fmt.Fprintln( + os.Stderr, + "WARNING: Found Kubernetes resources in install.yaml that are not "+ + "recognized by helm/v2-alpha scaffolding. "+ + "These resources were added under templates/extras.", + ) + } + // Write each group to appropriate template files for groupName, resources := range resourceGroups { if len(resources) > 0 { diff --git a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/extras_resources_test.go b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/extras_resources_test.go new file mode 100644 index 00000000000..1fdaeba8d9e --- /dev/null +++ b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/extras_resources_test.go @@ -0,0 +1,67 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kustomize + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +var _ = Describe("ResourceOrganizer extras handling", func() { + + It("moves unhandled resources into the extras group", func() { + // Known resource (handled) + deployment := &unstructured.Unstructured{} + deployment.SetAPIVersion("apps/v1") + deployment.SetKind("Deployment") + deployment.SetName("test-controller-manager") + + // Unknown / unscaffolded resource + service := &unstructured.Unstructured{} + service.SetAPIVersion("v1") + service.SetKind("Service") + service.SetName("custom-user-service") + + resources := &ParsedResources{ + AllResources: []*unstructured.Unstructured{ + deployment, + service, + }, + Deployment: deployment, + Services: []*unstructured.Unstructured{ + service, + }, + } + + organizer := NewResourceOrganizer(resources) + groups := organizer.OrganizeByFunction() + + // Deployment should be classified correctly + Expect(groups).To(HaveKey("manager")) + Expect(groups["manager"]).To(ContainElement(deployment)) + + // Service should NOT be dropped + Expect(groups).To(HaveKey("extras")) + Expect(groups["extras"]).To(ContainElement(service)) + + // Service must NOT be misclassified + Expect(groups).NotTo(HaveKey("metrics")) + Expect(groups).NotTo(HaveKey("webhook")) + }) +}) diff --git a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/parser.go b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/parser.go index 03fc737fb48..b8dce1de26b 100644 --- a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/parser.go +++ b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/parser.go @@ -28,6 +28,9 @@ import ( // ParsedResources holds Kubernetes resources organized by type for Helm chart generation type ParsedResources struct { + + // All resources parsed from install.yaml + AllResources []*unstructured.Unstructured // Core Kubernetes resources Namespace *unstructured.Unstructured Deployment *unstructured.Unstructured @@ -82,6 +85,7 @@ func (p *Parser) Parse() (*ParsedResources, error) { func (p *Parser) ParseFromReader(reader io.Reader) (*ParsedResources, error) { decoder := yaml.NewDecoder(reader) resources := &ParsedResources{ + AllResources: make([]*unstructured.Unstructured, 0), CustomResourceDefinitions: make([]*unstructured.Unstructured, 0), Roles: make([]*unstructured.Unstructured, 0), ClusterRoles: make([]*unstructured.Unstructured, 0), @@ -110,6 +114,8 @@ func (p *Parser) ParseFromReader(reader io.Reader) (*ParsedResources, error) { } obj := &unstructured.Unstructured{Object: doc} + // Track ALL resources + resources.AllResources = append(resources.AllResources, obj) p.categorizeResource(obj, resources) } @@ -175,3 +181,6 @@ func (pr *ParsedResources) EstimatePrefix(projectName string) string { } return prefix } +func (pr *ParsedResources) All() []*unstructured.Unstructured { + return pr.AllResources +} diff --git a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/resource_organizer.go b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/resource_organizer.go index 6dff347829b..8bc54ca97b7 100644 --- a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/resource_organizer.go +++ b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/resource_organizer.go @@ -38,49 +38,86 @@ func NewResourceOrganizer(resources *ParsedResources) *ResourceOrganizer { func (o *ResourceOrganizer) OrganizeByFunction() map[string][]*unstructured.Unstructured { groups := make(map[string][]*unstructured.Unstructured) + // Track resources that were explicitly handled + used := map[*unstructured.Unstructured]bool{} + // CRDs - Custom Resource Definitions if len(o.resources.CustomResourceDefinitions) > 0 { groups["crd"] = o.resources.CustomResourceDefinitions + for _, r := range o.resources.CustomResourceDefinitions { + used[r] = true + } } // RBAC - Role-Based Access Control resources rbacResources := o.collectRBACResources() if len(rbacResources) > 0 { groups["rbac"] = rbacResources + for _, r := range rbacResources { + used[r] = true + } } // Manager - Deployment and related resources if o.resources.Deployment != nil { groups["manager"] = []*unstructured.Unstructured{o.resources.Deployment} + used[o.resources.Deployment] = true } // Metrics - Metrics services and related resources metricsResources := o.collectMetricsResources() if len(metricsResources) > 0 { groups["metrics"] = metricsResources + for _, r := range metricsResources { + used[r] = true + } } // Webhook - Webhook configurations and webhook services webhookResources := o.collectWebhookResources() if len(webhookResources) > 0 { groups["webhook"] = webhookResources + for _, r := range webhookResources { + used[r] = true + } } // Cert-manager - Certificate issuers and related resources certManagerResources := o.collectCertManagerResources() if len(certManagerResources) > 0 { groups["cert-manager"] = certManagerResources + for _, r := range certManagerResources { + used[r] = true + } } // Prometheus - Prometheus ServiceMonitors and monitoring resources prometheusResources := o.collectPrometheusResources() if len(prometheusResources) > 0 { groups["prometheus"] = prometheusResources + for _, r := range prometheusResources { + used[r] = true + } } - // Other - Any remaining resources + // Other - Any remaining known resources if len(o.resources.Other) > 0 { groups["other"] = o.resources.Other + for _, r := range o.resources.Other { + used[r] = true + } + } + + // Extras - any resource from install.yaml not explicitly handled above + extras := []*unstructured.Unstructured{} + for _, r := range o.resources.All() { //ASSUMPTION: ParsedResources exposes All() + if !used[r] { + extras = append(extras, r) + } + } + + if len(extras) > 0 { + groups["extras"] = extras } return groups @@ -90,12 +127,10 @@ func (o *ResourceOrganizer) OrganizeByFunction() map[string][]*unstructured.Unst func (o *ResourceOrganizer) collectRBACResources() []*unstructured.Unstructured { var rbacResources []*unstructured.Unstructured - // Service account if o.resources.ServiceAccount != nil { rbacResources = append(rbacResources, o.resources.ServiceAccount) } - // Roles and bindings rbacResources = append(rbacResources, o.resources.Roles...) rbacResources = append(rbacResources, o.resources.ClusterRoles...) rbacResources = append(rbacResources, o.resources.RoleBindings...) @@ -108,10 +143,8 @@ func (o *ResourceOrganizer) collectRBACResources() []*unstructured.Unstructured func (o *ResourceOrganizer) collectWebhookResources() []*unstructured.Unstructured { var webhookResources []*unstructured.Unstructured - // Webhook configurations (ValidatingWebhookConfiguration, MutatingWebhookConfiguration) webhookResources = append(webhookResources, o.resources.WebhookConfigurations...) - // Webhook services (services containing "webhook" in the name) for _, service := range o.resources.Services { if o.isWebhookService(service) { webhookResources = append(webhookResources, service) @@ -125,12 +158,10 @@ func (o *ResourceOrganizer) collectWebhookResources() []*unstructured.Unstructur func (o *ResourceOrganizer) collectCertManagerResources() []*unstructured.Unstructured { var certManagerResources []*unstructured.Unstructured - // Certificate issuers if o.resources.Issuer != nil { certManagerResources = append(certManagerResources, o.resources.Issuer) } - // Certificates (both webhook and metrics certificates are cert-manager resources) certManagerResources = append(certManagerResources, o.resources.Certificates...) return certManagerResources @@ -140,7 +171,6 @@ func (o *ResourceOrganizer) collectCertManagerResources() []*unstructured.Unstru func (o *ResourceOrganizer) collectMetricsResources() []*unstructured.Unstructured { var metricsResources []*unstructured.Unstructured - // Metrics services (services containing "metrics" in the name) for _, service := range o.resources.Services { if o.isMetricsService(service) { metricsResources = append(metricsResources, service) @@ -153,21 +183,16 @@ func (o *ResourceOrganizer) collectMetricsResources() []*unstructured.Unstructur // collectPrometheusResources gathers prometheus related resources func (o *ResourceOrganizer) collectPrometheusResources() []*unstructured.Unstructured { var prometheusResources []*unstructured.Unstructured - - // ServiceMonitors prometheusResources = append(prometheusResources, o.resources.ServiceMonitors...) - return prometheusResources } // isWebhookService determines if a service is webhook-related func (o *ResourceOrganizer) isWebhookService(service *unstructured.Unstructured) bool { - serviceName := service.GetName() - return strings.Contains(serviceName, "webhook") + return strings.Contains(service.GetName(), "webhook") } // isMetricsService determines if a service is metrics-related func (o *ResourceOrganizer) isMetricsService(service *unstructured.Unstructured) bool { - serviceName := service.GetName() - return strings.Contains(serviceName, "metrics") + return strings.Contains(service.GetName(), "metrics") }