Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug fixs #880

Merged
merged 5 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"files": "go.sum|package-lock.json|^.secrets.baseline$",
"lines": null
},
"generated_at": "2024-10-07T16:10:03Z",
"generated_at": "2024-10-11T12:56:06Z",
"plugins_used": [
{
"name": "AWSKeyDetector"
Expand Down Expand Up @@ -100,7 +100,15 @@
"hashed_secret": "5996c731c43c6191af2324af0230bd65e723fcdb",
"is_secret": false,
"is_verified": false,
"line_number": 356,
"line_number": 357,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "03e60e3e0d9675b19754e2a81bbb48a26af858e7",
"is_secret": false,
"is_verified": false,
"line_number": 825,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down
139 changes: 107 additions & 32 deletions cloudinfo/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"math/rand"
"os"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -396,8 +397,8 @@ func (infoSvc *CloudInfoService) CreateStackFromConfigFile(stackConfig *ConfigDe
// Update stack inputs from catalog configuration
updateInputsFromCatalog(stackConfig, catalogConfig, catalogProductIndex, catalogFlavorIndex, doNotOverrideInputs)

// Check if all catalog inputs exist in the stack definition
checkCatalogInputsExistInStackDefinition(stackJson, catalogConfig, catalogProductIndex, catalogFlavorIndex, &errorMessages)
// Check if all catalog inputs exist in the stack definition and match types
validateCatalogInputsInStackDefinition(stackJson, catalogConfig, catalogProductIndex, catalogFlavorIndex, &errorMessages)

// If there are any errors from the validation process, return them
if len(errorMessages) > 0 {
Expand All @@ -407,8 +408,13 @@ func (infoSvc *CloudInfoService) CreateStackFromConfigFile(stackConfig *ConfigDe
// Sort stack inputs by name for consistency
sortInputsByName(stackConfig)

// Set stack name and description using catalog product and flavor labels
stackConfig.Name = fmt.Sprintf("%s-%s", catalogConfig.Products[catalogProductIndex].Label, catalogConfig.Products[catalogProductIndex].Flavors[catalogFlavorIndex].Label)
// Set stack name and description using catalog product and flavor labels, ensure the name is valid. For the test the name does not really matter so strip invalid characters.
// Define the pattern to match valid characters
var validNamePattern = regexp.MustCompile(`[^a-zA-Z0-9-_ ]`)

// Generate the stack name and strip invalid characters
rawName := fmt.Sprintf("%s-%s", catalogConfig.Products[catalogProductIndex].Label, catalogConfig.Products[catalogProductIndex].Flavors[catalogFlavorIndex].Label)
stackConfig.Name = validNamePattern.ReplaceAllString(rawName, "")
stackConfig.Description = fmt.Sprintf("%s-%s", catalogConfig.Products[catalogProductIndex].Label, catalogConfig.Products[catalogProductIndex].Flavors[catalogFlavorIndex].Label)

// Create the new stack
Expand Down Expand Up @@ -452,45 +458,34 @@ func checkStackForDuplicates(stackJson Stack) []string {
errorMessages := []string{}

inputNames := make(map[string]bool)
duplicateInputs := []string{}
for _, input := range stackJson.Inputs {
if _, exists := inputNames[input.Name]; exists {
duplicateInputs = append(duplicateInputs, input.Name)
errorMessages = append(errorMessages, fmt.Sprintf("duplicate stack input variable found: %s", input.Name))
} else {
inputNames[input.Name] = true
}
}
if len(duplicateInputs) > 0 {
errorMessages = append(errorMessages, fmt.Sprintf("duplicate stack input variable found: %s", strings.Join(duplicateInputs, ", ")))
}

outputNames := make(map[string]bool)
duplicateOutputs := []string{}
for _, output := range stackJson.Outputs {
if _, exists := outputNames[output.Name]; exists {
duplicateOutputs = append(duplicateOutputs, output.Name)
errorMessages = append(errorMessages, fmt.Sprintf("duplicate stack output variable found: %s", output.Name))
} else {
outputNames[output.Name] = true
}
}
if len(duplicateOutputs) > 0 {
errorMessages = append(errorMessages, fmt.Sprintf("duplicate stack output variable found: %s", strings.Join(duplicateOutputs, ", ")))
}

for _, member := range stackJson.Members {
memberInputNames := make(map[string]bool)
duplicateMemberInputs := []string{}
for _, input := range member.Inputs {
if _, exists := memberInputNames[input.Name]; exists {
duplicateMemberInputs = append(duplicateMemberInputs, fmt.Sprintf("member: %s input: %s", member.Name, input.Name))
errorMessages = append(errorMessages, fmt.Sprintf("duplicate member input variable found member: %s input: %s", member.Name, input.Name))
} else {
memberInputNames[input.Name] = true
}
}
if len(duplicateMemberInputs) > 0 {
errorMessages = append(errorMessages, fmt.Sprintf("duplicate member input variable found %s", strings.Join(duplicateMemberInputs, ", ")))
}
}

return errorMessages
}

Expand Down Expand Up @@ -622,6 +617,8 @@ func defineStackIO(stackJson Stack, stackConfig *ConfigDetails, doNotOverrideInp
// This function reads and unmarshals the catalog configuration, and identifies the product and flavor indices based on the stack configuration.
func readCatalogConfig(catalogJsonPath string, stackConfig *ConfigDetails, errorMessages *[]string) (CatalogJson, int, int, error) {
jsonFile, err := os.ReadFile(catalogJsonPath)
duplicateErrorMessages := []string{}

if err != nil {
log.Println("Error reading catalog JSON file:", err)
return CatalogJson{}, 0, 0, err
Expand Down Expand Up @@ -659,16 +656,18 @@ func readCatalogConfig(catalogJsonPath string, stackConfig *ConfigDetails, error
}

catalogInputNames := make(map[string]bool)
duplicateCatalogInputs := []string{}
productName := catalogConfig.Products[catalogProductIndex].Name
flavorName := catalogConfig.Products[catalogProductIndex].Flavors[catalogFlavorIndex].Name
for _, input := range catalogConfig.Products[catalogProductIndex].Flavors[catalogFlavorIndex].Configuration {
if _, exists := catalogInputNames[input.Key]; exists {
duplicateCatalogInputs = append(duplicateCatalogInputs, input.Key)
duplicateErrorMessages = append(duplicateErrorMessages, fmt.Sprintf("duplicate catalog input variable found in product '%s', flavor '%s': %s", productName, flavorName, input.Key))
} else {
catalogInputNames[input.Key] = true
}
}
if len(duplicateCatalogInputs) > 0 {
*errorMessages = append(*errorMessages, fmt.Sprintf("duplicate catalog input variable found: %s", strings.Join(duplicateCatalogInputs, ", ")))
if len(duplicateErrorMessages) > 0 {

*errorMessages = append(*errorMessages, strings.Join(duplicateErrorMessages, "\n"))
}

return catalogConfig, catalogProductIndex, catalogFlavorIndex, nil
Expand All @@ -694,6 +693,12 @@ func updateInputsFromCatalog(stackConfig *ConfigDetails, catalogConfig CatalogJs
} else {
inputDefault = doNotOverrideInputs["stack"][input.Key]
}

// Skip updating if the default value is nil
if inputDefault == nil {
continue
}

inputDefault = convertSliceToString(inputDefault)

found := false
Expand All @@ -718,7 +723,7 @@ func updateInputsFromCatalog(stackConfig *ConfigDetails, catalogConfig CatalogJs
if val, ok := inputDefault.(string); ok {
stackConfig.StackDefinition.Inputs[i].Default = &val
}
case "boolean":
case "bool", "boolean":
if val, ok := inputDefault.(bool); ok {
stackConfig.StackDefinition.Inputs[i].Default = &val
} else if val, err := strconv.ParseBool(inputDefault.(string)); err == nil {
Expand All @@ -742,30 +747,100 @@ func updateInputsFromCatalog(stackConfig *ConfigDetails, catalogConfig CatalogJs
}
}

// checkCatalogInputsExistInStackDefinition checks if all catalog inputs exist in the stack definition.
// This function ensures that each input defined in the catalog configuration is also present in the stack definition,
// validateCatalogInputsInStackDefinition validates that all catalog inputs exist in the stack definition and their types match.
// This function ensures that each input defined in the catalog configuration is also present in the stack definition with the correct type,
// thereby ensuring consistency between the catalog and stack configurations.
func checkCatalogInputsExistInStackDefinition(stackJson Stack, catalogConfig CatalogJson, catalogProductIndex, catalogFlavorIndex int, errorMessages *[]string) {
func validateCatalogInputsInStackDefinition(stackJson Stack, catalogConfig CatalogJson, catalogProductIndex, catalogFlavorIndex int, errorMessages *[]string) {
catalogInputs := catalogConfig.Products[catalogProductIndex].Flavors[catalogFlavorIndex].Configuration
missingInputs := []string{}
productName := catalogConfig.Products[catalogProductIndex].Name
flavorName := catalogConfig.Products[catalogProductIndex].Flavors[catalogFlavorIndex].Name
typeMismatches := []string{}
defaultTypeMismatches := []string{}
extraInputs := []string{}

// Iterate over each catalog input and check if it exists in the stack definition
// Iterate over each catalog input and check if it exists in the stack definition and if the types match
for _, catalogInput := range catalogInputs {
// Skip the ibmcloud_api_key input as it may not part of the stack definition, but is always valid
if catalogInput.Key == "ibmcloud_api_key" {
continue
}
found := false
for _, stackInput := range stackJson.Inputs {
if catalogInput.Key == stackInput.Name {
found = true
expectedType := convertGoTypeToExpectedType(stackInput.Type)
if !isValidType(catalogInput.Type, expectedType) {
typeMismatches = append(typeMismatches, fmt.Sprintf("catalog configuration type mismatch in product '%s', flavor '%s': %s expected type: %s, got: %s", productName, flavorName, catalogInput.Key, expectedType, catalogInput.Type))
}
// Check if the default value type matches the expected type
if catalogInput.DefaultValue != nil {
defaultValueType := reflect.TypeOf(catalogInput.DefaultValue).String()
expectedDefaultValueType := convertGoTypeToExpectedType(defaultValueType)
if !isValidType(expectedType, expectedDefaultValueType) {
defaultTypeMismatches = append(defaultTypeMismatches, fmt.Sprintf("catalog configuration default value type mismatch in product '%s', flavor '%s': %s expected type: %s, got: %s", productName, flavorName, catalogInput.Key, expectedType, expectedDefaultValueType))
}
}
break
}
}
if !found {
missingInputs = append(missingInputs, catalogInput.Key)
extraInputs = append(extraInputs, fmt.Sprintf("extra catalog input variable not found in stack definition in product '%s', flavor '%s': %s", productName, flavorName, catalogInput.Key))
}
}

if len(missingInputs) > 0 {
*errorMessages = append(*errorMessages, fmt.Sprintf("catalog input variable not found in stack definition: %s", strings.Join(missingInputs, ", ")))
if len(typeMismatches) > 0 {
*errorMessages = append(*errorMessages, strings.Join(typeMismatches, "\n"))
}

if len(defaultTypeMismatches) > 0 {
*errorMessages = append(*errorMessages, strings.Join(defaultTypeMismatches, "\n"))
}

if len(extraInputs) > 0 {
*errorMessages = append(*errorMessages, strings.Join(extraInputs, "\n"))
}
}

// convertGoTypeToExpectedType converts Go types to the expected type names as defined in catalog json.
func convertGoTypeToExpectedType(goType string) string {
switch goType {
case "string":
return "string"
case "int", "int64", "float32", "float64":
return "int"
case "[]interface {}":
return "array"
case "map[string]interface {}":
return "object"
case "bool":
return "bool"
default:
return goType
}
}

// isValidType checks if the provided type is valid considering additional valid types
func isValidType(expectedType, actualType string) bool {
validTypes := map[string][]string{
"password": {"password", "string"},
"boolean": {"boolean", "bool"},
"array": {"array"},
"object": {"object"},
"int": {"int", "int64", "float32", "float64"},
"string": {"string"},
"float": {"float32", "float64"},
// Add more type mappings as needed
}

if valid, exists := validTypes[expectedType]; exists {
for _, t := range valid {
if t == actualType {
return true
}
}
return false
}
return expectedType == actualType
}

// sortInputsByName sorts the stack inputs by their name.
Expand Down
Loading