mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-04-15 07:16:34 +00:00
633 lines
20 KiB
Go
633 lines
20 KiB
Go
package convert
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// V1Beta2ToV1Beta3Result holds the conversion results
|
|
type V1Beta2ToV1Beta3Result struct {
|
|
TemplatedSpec string `yaml:"-"`
|
|
ValuesFile string `yaml:"-"`
|
|
Values map[string]interface{} `yaml:"-"`
|
|
}
|
|
|
|
// ConvertToV1Beta3 converts a v1beta2 preflight spec to v1beta3 format with templating
|
|
func ConvertToV1Beta3(doc []byte) (*V1Beta2ToV1Beta3Result, error) {
|
|
var parsed map[string]interface{}
|
|
err := yaml.Unmarshal(doc, &parsed)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to unmarshal yaml")
|
|
}
|
|
|
|
// Check if it's already v1beta3
|
|
if apiVersion, ok := parsed["apiVersion"]; ok && apiVersion == "troubleshoot.sh/v1beta3" {
|
|
return nil, errors.New("document is already v1beta3")
|
|
}
|
|
|
|
// Check if it's v1beta2
|
|
if apiVersion, ok := parsed["apiVersion"]; !ok || apiVersion != "troubleshoot.sh/v1beta2" {
|
|
return nil, errors.Errorf("unsupported apiVersion: %v", apiVersion)
|
|
}
|
|
|
|
// Check if it's a preflight spec
|
|
if kind, ok := parsed["kind"]; !ok || kind != "Preflight" {
|
|
return nil, errors.Errorf("unsupported kind: %v", kind)
|
|
}
|
|
|
|
// Extract values and create templated spec
|
|
values := make(map[string]interface{})
|
|
converter := &v1beta3Converter{
|
|
values: values,
|
|
spec: parsed,
|
|
}
|
|
|
|
templatedSpec, err := converter.convert()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to convert spec")
|
|
}
|
|
|
|
// Marshal values
|
|
valuesBytes, err := yaml.Marshal(values)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to marshal values")
|
|
}
|
|
|
|
return &V1Beta2ToV1Beta3Result{
|
|
TemplatedSpec: templatedSpec,
|
|
ValuesFile: string(valuesBytes),
|
|
Values: values,
|
|
}, nil
|
|
}
|
|
|
|
type v1beta3Converter struct {
|
|
values map[string]interface{}
|
|
spec map[string]interface{}
|
|
}
|
|
|
|
func (c *v1beta3Converter) convert() (string, error) {
|
|
// Initialize values structure
|
|
c.initializeValues()
|
|
|
|
// Get metadata name
|
|
metadataName := "converted-from-v1beta2"
|
|
if metadata, ok := c.spec["metadata"].(map[interface{}]interface{}); ok {
|
|
if name, ok := metadata["name"].(string); ok {
|
|
metadataName = name
|
|
}
|
|
}
|
|
|
|
// Process spec
|
|
var analyzers []interface{}
|
|
if spec, ok := c.spec["spec"].(map[interface{}]interface{}); ok {
|
|
if analyzersList, ok := spec["analyzers"].([]interface{}); ok {
|
|
convertedAnalyzers, err := c.convertAnalyzers(analyzersList)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to convert analyzers")
|
|
}
|
|
analyzers = convertedAnalyzers
|
|
}
|
|
}
|
|
|
|
// Build the templated spec string
|
|
var buf bytes.Buffer
|
|
|
|
// Header
|
|
buf.WriteString("apiVersion: troubleshoot.sh/v1beta3\n")
|
|
buf.WriteString("kind: Preflight\n")
|
|
buf.WriteString("metadata:\n")
|
|
buf.WriteString(fmt.Sprintf(" name: %s\n", metadataName))
|
|
buf.WriteString("spec:\n")
|
|
buf.WriteString(" analyzers:\n")
|
|
|
|
// Add each analyzer
|
|
for _, analyzer := range analyzers {
|
|
if analyzerStr, ok := analyzer.(string); ok {
|
|
// This is already a templated string
|
|
buf.WriteString(" ")
|
|
buf.WriteString(strings.ReplaceAll(analyzerStr, "\n", "\n "))
|
|
buf.WriteString("\n")
|
|
} else {
|
|
// Convert to YAML and add as-is
|
|
analyzerBytes, err := yaml.Marshal(analyzer)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to marshal analyzer")
|
|
}
|
|
lines := strings.Split(string(analyzerBytes), "\n")
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) != "" {
|
|
buf.WriteString(" - ")
|
|
buf.WriteString(line)
|
|
buf.WriteString("\n")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return buf.String(), nil
|
|
}
|
|
|
|
func (c *v1beta3Converter) initializeValues() {
|
|
c.values["kubernetes"] = map[string]interface{}{
|
|
"enabled": false,
|
|
"minVersion": "1.20.0",
|
|
"recommendedVersion": "1.22.0",
|
|
}
|
|
|
|
c.values["storage"] = map[string]interface{}{
|
|
"enabled": false,
|
|
"className": "default",
|
|
}
|
|
|
|
c.values["cluster"] = map[string]interface{}{
|
|
"minNodes": 3,
|
|
"recommendedNodes": 5,
|
|
"minCPU": 4,
|
|
}
|
|
|
|
c.values["node"] = map[string]interface{}{
|
|
"minMemoryGi": 8,
|
|
"recommendedMemoryGi": 32,
|
|
"minEphemeralGi": 40,
|
|
"recommendedEphemeralGi": 100,
|
|
}
|
|
|
|
c.values["ingress"] = map[string]interface{}{
|
|
"enabled": false,
|
|
"type": "Contour",
|
|
}
|
|
|
|
c.values["runtime"] = map[string]interface{}{
|
|
"enabled": false,
|
|
}
|
|
|
|
c.values["distribution"] = map[string]interface{}{
|
|
"enabled": false,
|
|
}
|
|
|
|
c.values["nodeChecks"] = map[string]interface{}{
|
|
"enabled": false,
|
|
"count": map[string]interface{}{
|
|
"enabled": false,
|
|
},
|
|
"cpu": map[string]interface{}{
|
|
"enabled": false,
|
|
},
|
|
"memory": map[string]interface{}{
|
|
"enabled": false,
|
|
},
|
|
"ephemeral": map[string]interface{}{
|
|
"enabled": false,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (c *v1beta3Converter) convertAnalyzers(analyzers []interface{}) ([]interface{}, error) {
|
|
var result []interface{}
|
|
|
|
for _, analyzer := range analyzers {
|
|
if analyzerMap, ok := analyzer.(map[interface{}]interface{}); ok {
|
|
converted, err := c.convertAnalyzer(analyzerMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if converted != nil {
|
|
result = append(result, converted)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (c *v1beta3Converter) convertAnalyzer(analyzer map[interface{}]interface{}) (interface{}, error) {
|
|
// Convert analyzer based on type
|
|
if _, exists := analyzer["clusterVersion"]; exists {
|
|
return c.convertClusterVersion(analyzer)
|
|
}
|
|
|
|
if _, exists := analyzer["customResourceDefinition"]; exists {
|
|
return c.convertCustomResourceDefinition(analyzer)
|
|
}
|
|
|
|
if _, exists := analyzer["containerRuntime"]; exists {
|
|
return c.convertContainerRuntime(analyzer)
|
|
}
|
|
|
|
if _, exists := analyzer["storageClass"]; exists {
|
|
return c.convertStorageClass(analyzer)
|
|
}
|
|
|
|
if _, exists := analyzer["distribution"]; exists {
|
|
return c.convertDistribution(analyzer)
|
|
}
|
|
|
|
if _, exists := analyzer["nodeResources"]; exists {
|
|
return c.convertNodeResources(analyzer)
|
|
}
|
|
|
|
// For unrecognized analyzers, return as-is with warning comment
|
|
return c.wrapWithWarning(analyzer, "Unknown analyzer type - manual review required")
|
|
}
|
|
|
|
func (c *v1beta3Converter) convertClusterVersion(analyzer map[interface{}]interface{}) (interface{}, error) {
|
|
// Enable kubernetes checks
|
|
c.setNestedValue("kubernetes.enabled", true)
|
|
|
|
// Extract version requirements from outcomes
|
|
if cv, ok := analyzer["clusterVersion"].(map[interface{}]interface{}); ok {
|
|
if outcomes, ok := cv["outcomes"].([]interface{}); ok {
|
|
c.extractVersionRequirements(outcomes)
|
|
}
|
|
}
|
|
|
|
return c.createTemplatedAnalyzer("kubernetes", analyzer, "")
|
|
}
|
|
|
|
func (c *v1beta3Converter) convertCustomResourceDefinition(analyzer map[interface{}]interface{}) (interface{}, error) {
|
|
c.setNestedValue("ingress.enabled", true)
|
|
|
|
if crd, ok := analyzer["customResourceDefinition"].(map[interface{}]interface{}); ok {
|
|
if crdName, ok := crd["customResourceDefinitionName"].(string); ok {
|
|
if strings.Contains(crdName, "contour") {
|
|
c.setNestedValue("ingress.type", "Contour")
|
|
}
|
|
}
|
|
}
|
|
|
|
return c.createTemplatedAnalyzer("ingress", analyzer, "")
|
|
}
|
|
|
|
func (c *v1beta3Converter) convertContainerRuntime(analyzer map[interface{}]interface{}) (interface{}, error) {
|
|
c.setNestedValue("runtime.enabled", true)
|
|
|
|
return c.createTemplatedAnalyzer("runtime", analyzer, "")
|
|
}
|
|
|
|
func (c *v1beta3Converter) convertStorageClass(analyzer map[interface{}]interface{}) (interface{}, error) {
|
|
c.setNestedValue("storage.enabled", true)
|
|
|
|
// Extract storage class name
|
|
if sc, ok := analyzer["storageClass"].(map[interface{}]interface{}); ok {
|
|
if className, ok := sc["storageClassName"].(string); ok {
|
|
c.setNestedValue("storage.className", className)
|
|
}
|
|
}
|
|
|
|
// Update the analyzer to use template
|
|
if sc, ok := analyzer["storageClass"].(map[interface{}]interface{}); ok {
|
|
sc["storageClassName"] = "{{ .Values.storage.className }}"
|
|
}
|
|
|
|
return c.createTemplatedAnalyzer("storage", analyzer, "")
|
|
}
|
|
|
|
func (c *v1beta3Converter) convertDistribution(analyzer map[interface{}]interface{}) (interface{}, error) {
|
|
c.setNestedValue("distribution.enabled", true)
|
|
|
|
return c.createTemplatedAnalyzer("distribution", analyzer, "")
|
|
}
|
|
|
|
func (c *v1beta3Converter) convertNodeResources(analyzer map[interface{}]interface{}) (interface{}, error) {
|
|
if nr, ok := analyzer["nodeResources"].(map[interface{}]interface{}); ok {
|
|
checkName := ""
|
|
if name, ok := nr["checkName"].(string); ok {
|
|
checkName = strings.ToLower(name)
|
|
}
|
|
|
|
// Determine node resource type and enable appropriate check
|
|
if strings.Contains(checkName, "node") && strings.Contains(checkName, "count") {
|
|
c.setNestedValue("nodeChecks.enabled", true)
|
|
c.setNestedValue("nodeChecks.count.enabled", true)
|
|
c.extractNodeCountRequirements(nr)
|
|
return c.createTemplatedAnalyzer("nodeChecks.count", analyzer, "")
|
|
}
|
|
|
|
if strings.Contains(checkName, "cpu") || strings.Contains(checkName, "core") {
|
|
c.setNestedValue("nodeChecks.enabled", true)
|
|
c.setNestedValue("nodeChecks.cpu.enabled", true)
|
|
c.extractCPURequirements(nr)
|
|
return c.createTemplatedAnalyzer("nodeChecks.cpu", analyzer, "")
|
|
}
|
|
|
|
if strings.Contains(checkName, "memory") {
|
|
c.setNestedValue("nodeChecks.enabled", true)
|
|
c.setNestedValue("nodeChecks.memory.enabled", true)
|
|
c.extractMemoryRequirements(nr)
|
|
c.templatizeMemoryOutcomes(analyzer)
|
|
return c.createTemplatedAnalyzer("nodeChecks.memory", analyzer, "")
|
|
}
|
|
|
|
if strings.Contains(checkName, "ephemeral") || strings.Contains(checkName, "storage") {
|
|
c.setNestedValue("nodeChecks.enabled", true)
|
|
c.setNestedValue("nodeChecks.ephemeral.enabled", true)
|
|
c.extractEphemeralRequirements(nr)
|
|
c.templatizeEphemeralOutcomes(analyzer)
|
|
return c.createTemplatedAnalyzer("nodeChecks.ephemeral", analyzer, "")
|
|
}
|
|
}
|
|
|
|
// Default case - enable general node checks
|
|
c.setNestedValue("nodeChecks.enabled", true)
|
|
return c.createTemplatedAnalyzer("nodeChecks", analyzer, "")
|
|
}
|
|
|
|
func (c *v1beta3Converter) createTemplatedAnalyzer(checkType string, originalAnalyzer map[interface{}]interface{}, docString string) (interface{}, error) {
|
|
// Convert map[interface{}]interface{} to map[string]interface{} for proper YAML output
|
|
convertedAnalyzer := c.convertMapKeys(originalAnalyzer)
|
|
|
|
// Add placeholder docString - user should replace with their actual requirements
|
|
convertedAnalyzer["docString"] = "# TODO: Add docString with Title, Requirement, and rationale for this check"
|
|
|
|
// Marshal the analyzer to YAML
|
|
analyzerBytes, err := yaml.Marshal(convertedAnalyzer)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to marshal analyzer")
|
|
}
|
|
|
|
// Create template string with proper indentation
|
|
analyzerYAML := strings.TrimSuffix(string(analyzerBytes), "\n")
|
|
|
|
// Add conditional wrapper
|
|
condition := fmt.Sprintf("{{- if .Values.%s.enabled }}", checkType)
|
|
endCondition := "{{- end }}"
|
|
|
|
templateStr := fmt.Sprintf("%s\n- %s\n%s", condition,
|
|
strings.ReplaceAll(analyzerYAML, "\n", "\n "),
|
|
endCondition)
|
|
|
|
return templateStr, nil
|
|
}
|
|
|
|
func (c *v1beta3Converter) wrapWithWarning(analyzer map[interface{}]interface{}, warning string) (interface{}, error) {
|
|
convertedAnalyzer := c.convertMapKeys(analyzer)
|
|
convertedAnalyzer["docString"] = fmt.Sprintf("# TODO: Manual Review Required - %s", warning)
|
|
return convertedAnalyzer, nil
|
|
}
|
|
|
|
func (c *v1beta3Converter) convertMapKeys(m map[interface{}]interface{}) map[string]interface{} {
|
|
result := make(map[string]interface{})
|
|
for k, v := range m {
|
|
strKey := fmt.Sprintf("%v", k)
|
|
switch val := v.(type) {
|
|
case map[interface{}]interface{}:
|
|
result[strKey] = c.convertMapKeys(val)
|
|
case []interface{}:
|
|
result[strKey] = c.convertSlice(val)
|
|
default:
|
|
result[strKey] = val
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (c *v1beta3Converter) convertSlice(s []interface{}) []interface{} {
|
|
result := make([]interface{}, len(s))
|
|
for i, v := range s {
|
|
switch val := v.(type) {
|
|
case map[interface{}]interface{}:
|
|
result[i] = c.convertMapKeys(val)
|
|
case []interface{}:
|
|
result[i] = c.convertSlice(val)
|
|
default:
|
|
result[i] = val
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Helper methods for extracting requirements from outcomes
|
|
func (c *v1beta3Converter) extractVersionRequirements(outcomes []interface{}) {
|
|
for _, outcome := range outcomes {
|
|
if outcomeMap, ok := outcome.(map[interface{}]interface{}); ok {
|
|
if fail, ok := outcomeMap["fail"].(map[interface{}]interface{}); ok {
|
|
if when, ok := fail["when"].(string); ok {
|
|
if version := c.extractVersionFromWhen(when); version != "" {
|
|
c.setNestedValue("kubernetes.minVersion", version)
|
|
}
|
|
}
|
|
}
|
|
if warn, ok := outcomeMap["warn"].(map[interface{}]interface{}); ok {
|
|
if when, ok := warn["when"].(string); ok {
|
|
if version := c.extractVersionFromWhen(when); version != "" {
|
|
c.setNestedValue("kubernetes.recommendedVersion", version)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *v1beta3Converter) extractVersionFromWhen(when string) string {
|
|
// Simple version extraction from conditions like "< 1.22.0"
|
|
when = strings.TrimSpace(when)
|
|
if strings.HasPrefix(when, "<") {
|
|
version := strings.TrimSpace(strings.TrimPrefix(when, "<"))
|
|
version = strings.Trim(version, `"`)
|
|
return version
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (c *v1beta3Converter) extractNodeCountRequirements(nr map[interface{}]interface{}) {
|
|
if outcomes, ok := nr["outcomes"].([]interface{}); ok {
|
|
for _, outcome := range outcomes {
|
|
if outcomeMap, ok := outcome.(map[interface{}]interface{}); ok {
|
|
if fail, ok := outcomeMap["fail"].(map[interface{}]interface{}); ok {
|
|
if when, ok := fail["when"].(string); ok {
|
|
if count := c.extractNumberFromWhen(when, "count()"); count > 0 {
|
|
c.setNestedValue("cluster.minNodes", count)
|
|
}
|
|
}
|
|
}
|
|
if warn, ok := outcomeMap["warn"].(map[interface{}]interface{}); ok {
|
|
if when, ok := warn["when"].(string); ok {
|
|
if count := c.extractNumberFromWhen(when, "count()"); count > 0 {
|
|
c.setNestedValue("cluster.recommendedNodes", count)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *v1beta3Converter) extractCPURequirements(nr map[interface{}]interface{}) {
|
|
if outcomes, ok := nr["outcomes"].([]interface{}); ok {
|
|
for _, outcome := range outcomes {
|
|
if outcomeMap, ok := outcome.(map[interface{}]interface{}); ok {
|
|
if fail, ok := outcomeMap["fail"].(map[interface{}]interface{}); ok {
|
|
if when, ok := fail["when"].(string); ok {
|
|
if cpu := c.extractNumberFromWhen(when, "sum(cpuCapacity)"); cpu > 0 {
|
|
c.setNestedValue("cluster.minCPU", cpu)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *v1beta3Converter) extractMemoryRequirements(nr map[interface{}]interface{}) {
|
|
if outcomes, ok := nr["outcomes"].([]interface{}); ok {
|
|
for _, outcome := range outcomes {
|
|
if outcomeMap, ok := outcome.(map[interface{}]interface{}); ok {
|
|
if fail, ok := outcomeMap["fail"].(map[interface{}]interface{}); ok {
|
|
if when, ok := fail["when"].(string); ok {
|
|
if memory := c.extractMemoryFromWhen(when); memory > 0 {
|
|
c.setNestedValue("node.minMemoryGi", memory)
|
|
}
|
|
}
|
|
}
|
|
if warn, ok := outcomeMap["warn"].(map[interface{}]interface{}); ok {
|
|
if when, ok := warn["when"].(string); ok {
|
|
if memory := c.extractMemoryFromWhen(when); memory > 0 {
|
|
c.setNestedValue("node.recommendedMemoryGi", memory)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *v1beta3Converter) extractEphemeralRequirements(nr map[interface{}]interface{}) {
|
|
if outcomes, ok := nr["outcomes"].([]interface{}); ok {
|
|
for _, outcome := range outcomes {
|
|
if outcomeMap, ok := outcome.(map[interface{}]interface{}); ok {
|
|
if fail, ok := outcomeMap["fail"].(map[interface{}]interface{}); ok {
|
|
if when, ok := fail["when"].(string); ok {
|
|
if storage := c.extractStorageFromWhen(when); storage > 0 {
|
|
c.setNestedValue("node.minEphemeralGi", storage)
|
|
}
|
|
}
|
|
}
|
|
if warn, ok := outcomeMap["warn"].(map[interface{}]interface{}); ok {
|
|
if when, ok := warn["when"].(string); ok {
|
|
if storage := c.extractStorageFromWhen(when); storage > 0 {
|
|
c.setNestedValue("node.recommendedEphemeralGi", storage)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *v1beta3Converter) extractNumberFromWhen(when, prefix string) int {
|
|
when = strings.TrimSpace(when)
|
|
if strings.Contains(when, prefix) {
|
|
// Extract number from conditions like "count() < 3"
|
|
parts := strings.Split(when, "<")
|
|
if len(parts) == 2 {
|
|
numStr := strings.TrimSpace(parts[1])
|
|
if num, err := strconv.Atoi(numStr); err == nil {
|
|
return num
|
|
}
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (c *v1beta3Converter) extractMemoryFromWhen(when string) int {
|
|
when = strings.TrimSpace(when)
|
|
// Handle conditions like "min(memoryCapacity) < 8Gi"
|
|
if strings.Contains(when, "memoryCapacity") {
|
|
parts := strings.Split(when, "<")
|
|
if len(parts) == 2 {
|
|
sizeStr := strings.TrimSpace(parts[1])
|
|
sizeStr = strings.TrimSuffix(sizeStr, "Gi")
|
|
if num, err := strconv.Atoi(sizeStr); err == nil {
|
|
return num
|
|
}
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (c *v1beta3Converter) extractStorageFromWhen(when string) int {
|
|
when = strings.TrimSpace(when)
|
|
// Handle conditions like "min(ephemeralStorageCapacity) < 40Gi"
|
|
if strings.Contains(when, "ephemeralStorageCapacity") {
|
|
parts := strings.Split(when, "<")
|
|
if len(parts) == 2 {
|
|
sizeStr := strings.TrimSpace(parts[1])
|
|
sizeStr = strings.TrimSuffix(sizeStr, "Gi")
|
|
if num, err := strconv.Atoi(sizeStr); err == nil {
|
|
return num
|
|
}
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (c *v1beta3Converter) templatizeMemoryOutcomes(analyzer map[interface{}]interface{}) {
|
|
c.templatizeNodeResourceOutcomes(analyzer, "memoryCapacity", "node.minMemoryGi", "node.recommendedMemoryGi")
|
|
}
|
|
|
|
func (c *v1beta3Converter) templatizeEphemeralOutcomes(analyzer map[interface{}]interface{}) {
|
|
c.templatizeNodeResourceOutcomes(analyzer, "ephemeralStorageCapacity", "node.minEphemeralGi", "node.recommendedEphemeralGi")
|
|
}
|
|
|
|
func (c *v1beta3Converter) templatizeNodeResourceOutcomes(analyzer map[interface{}]interface{}, capacity, minKey, recKey string) {
|
|
if nr, ok := analyzer["nodeResources"].(map[interface{}]interface{}); ok {
|
|
if outcomes, ok := nr["outcomes"].([]interface{}); ok {
|
|
for _, outcome := range outcomes {
|
|
if outcomeMap, ok := outcome.(map[interface{}]interface{}); ok {
|
|
// Update fail condition
|
|
if fail, ok := outcomeMap["fail"].(map[interface{}]interface{}); ok {
|
|
if when, ok := fail["when"].(string); ok && strings.Contains(when, capacity) {
|
|
fail["when"] = fmt.Sprintf("min(%s) < {{ .Values.%s }}Gi", capacity, minKey)
|
|
}
|
|
if _, ok := fail["message"].(string); ok {
|
|
parts := strings.Split(minKey, ".")
|
|
fail["message"] = fmt.Sprintf("All nodes must have at least {{ .Values.%s }} GiB of %s.", minKey, parts[len(parts)-1])
|
|
}
|
|
}
|
|
// Update warn condition
|
|
if warn, ok := outcomeMap["warn"].(map[interface{}]interface{}); ok {
|
|
if when, ok := warn["when"].(string); ok && strings.Contains(when, capacity) {
|
|
warn["when"] = fmt.Sprintf("min(%s) < {{ .Values.%s }}Gi", capacity, recKey)
|
|
}
|
|
if _, ok := warn["message"].(string); ok {
|
|
parts := strings.Split(recKey, ".")
|
|
warn["message"] = fmt.Sprintf("All nodes are recommended to have at least {{ .Values.%s }} GiB of %s.", recKey, parts[len(parts)-1])
|
|
}
|
|
}
|
|
// Update pass message
|
|
if pass, ok := outcomeMap["pass"].(map[interface{}]interface{}); ok {
|
|
if _, ok := pass["message"].(string); ok {
|
|
parts := strings.Split(recKey, ".")
|
|
pass["message"] = fmt.Sprintf("All nodes have at least {{ .Values.%s }} GiB of %s.", recKey, parts[len(parts)-1])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *v1beta3Converter) setNestedValue(path string, value interface{}) {
|
|
parts := strings.Split(path, ".")
|
|
current := c.values
|
|
|
|
for _, part := range parts[:len(parts)-1] {
|
|
if _, ok := current[part]; !ok {
|
|
current[part] = make(map[string]interface{})
|
|
}
|
|
if nextMap, ok := current[part].(map[string]interface{}); ok {
|
|
current = nextMap
|
|
} else {
|
|
// Path exists but isn't a map, need to handle this case
|
|
return
|
|
}
|
|
}
|
|
|
|
current[parts[len(parts)-1]] = value
|
|
}
|