package action import ( "fmt" "os" "strings" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/releaseutil" ) func GetInstallSequence(ch *chart.Chart, manifests []releaseutil.Manifest, resources kube.ResourceList) ([]InstallItem, error) { if len(ch.Metadata.Install) == 0 { return []InstallItem{ { ChartName: ch.Name(), WaitFor: "", Resources: resources, }, }, nil } // 构建 manifest content -> chart name 的映射 // 用资源的 Kind+Name 作为 key 来匹配 type resourceKey struct { kind string name string } manifestChartMap := make(map[resourceKey]string) for _, m := range manifests { if m.Head == nil || m.Head.Metadata == nil { fmt.Fprintf(os.Stdout, "DEBUG: invalid manifest: %s in %s", m.Content, m.Name) continue } chartName := extractChartNameFromPath(m.Name) // 从 manifest content 中解析出 kind 和 name kind := m.Head.Kind name := m.Head.Metadata.Name if kind != "" && name != "" { manifestChartMap[resourceKey{kind: kind, name: name}] = chartName } } // 构建 waitFor 映射 waitForMap := make(map[string]string) for _, inst := range ch.Metadata.Install { waitForMap[inst.Name] = inst.WaitFor } // 按 chart 分组 groupMap := make(map[string]*InstallItem) var order []string for _, inst := range ch.Metadata.Install { groupMap[inst.Name] = &InstallItem{ ChartName: inst.Name, WaitFor: inst.WaitFor, Resources: kube.ResourceList{}, } order = append(order, inst.Name) } // 遍历 resources,通过 Kind+Name 找到对应的 chart for _, r := range resources { kind := r.Mapping.GroupVersionKind.Kind name := r.Name key := resourceKey{kind: kind, name: name} chartName, found := manifestChartMap[key] if !found { chartName = ch.Name() // fallback 到主 chart } if group, exists := groupMap[chartName]; exists { group.Resources = append(group.Resources, r) } else { groupMap[chartName] = &InstallItem{ ChartName: chartName, WaitFor: waitForMap[chartName], Resources: kube.ResourceList{r}, } order = append(order, chartName) } } result := make([]InstallItem, 0, len(order)) for _, name := range order { if item := groupMap[name]; item != nil && len(item.Resources) > 0 { result = append(result, *item) } } return result, nil } // 从 manifest 路径中提取 chart name func extractChartNameFromPath(filePath string) string { parts := strings.Split(filePath, "/") for i, part := range parts { if part == "charts" && i+1 < len(parts) { return parts[i+1] } } if len(parts) >= 1 { return parts[0] } return filePath } // 简单解析 manifest content 获取 kind 和 name func parseKindAndName(content string) (kind string, name string) { lines := strings.Split(content, "\n") inMetadata := false for _, line := range lines { trimmed := strings.TrimSpace(line) // 解析 kind if strings.HasPrefix(trimmed, "kind:") { kind = strings.TrimSpace(strings.TrimPrefix(trimmed, "kind:")) } // 进入 metadata 块 if trimmed == "metadata:" { inMetadata = true continue } // 在 metadata 块中找 name if inMetadata && strings.HasPrefix(trimmed, "name:") { name = strings.TrimSpace(strings.TrimPrefix(trimmed, "name:")) // 移除可能的引号 name = strings.Trim(name, "\"'") break } // 离开 metadata 块 if inMetadata && !strings.HasPrefix(line, " ") && !strings.HasPrefix(line, "\t") && trimmed != "" { inMetadata = false } } return kind, name }