DEV Community

Felix
Felix

Posted on

Selefra-How to Read Code to Provide for Rule Use

Image description

Selefra Code Reading

In the previous article, we discussed how Selefra utilizes Provider to fetch data. Now, let's delve into the details of how Selefra reads YAML code.

Code Block Reading Methods

As a Policy-as-Code tool, Selefra plays a crucial role in code reading. Simple YAML parsing is not sufficient due to the interdependencies and associations between code blocks. We need to read different sections of code separately, establish connections based on keywords, and merge them accordingly. Additionally, it's essential to perform validity checks on the code and report any errors in location and content. Therefore, we need to handle this functionality with special treatment.

Let's first understand the basic methods for reading code blocks and performing validity checks during the reading process.

Selefra and Provider Modules

Selefra and Provider modules are relatively straightforward, with no complex compliance relationships. We only need to define the structure and perform checks. Here's an example code snippet showcasing the Provider module:

// Insert code model here
type SelefraBlock struct {

    // Name of project
    Name string `yaml:"name,omitempty" mapstructure:"name,omitempty"`

    // selefra CloudBlock-related configuration
    CloudBlock *CloudBlock `yaml:"cloud,omitempty" mapstructure:"cloud,omitempty"`

    OpenaiApiKey string `yaml:"openai_api_key,omitempty" mapstructure:"openai_api_key,omitempty"`
    OpenaiMode   string `yaml:"openai_mode,omitempty" mapstructure:"openai_mode,omitempty"`
    OpenaiLimit  uint64 `yaml:"openai_limit,omitempty" mapstructure:"openai_limit,omitempty"`

    // The version of the cli used by the project
    CliVersion string `yaml:"cli_version,omitempty" mapstructure:"cli_version,omitempty"`

    // Global log level. This level is used when the provider does not specify a log level
    LogLevel string `yaml:"log_level,omitempty" mapstructure:"log_level,omitempty"`

    //What are the providers required for operation
    RequireProvidersBlock RequireProvidersBlock `yaml:"providers,omitempty" mapstructure:"providers,omitempty"`

    // The configuration required to connect to the database
    ConnectionBlock *ConnectionBlock `yaml:"connection,omitempty" mapstructure:"connection,omitempty"`

    *LocatableImpl `yaml:"-"`
}

Enter fullscreen mode Exit fullscreen mode

We have defined the structure, and for each code block, we have defined the parseSelefraBlock method within the YamlFileToModuleParser structure to load and validate the Selefra code blocks.

func (x *YamlFileToModuleParser) parseSelefraBlock(selefraBlockKeyNode, selefraBlockValueNode *yaml.Node, diagnostics *schema.Diagnostics) *module.SelefraBlock {

    blockPath := SelefraBlockFieldName

    // Type check
    if selefraBlockValueNode.Kind != yaml.MappingNode {
        diagnostics.AddDiagnostics(x.buildNodeErrorMsgForMappingType(selefraBlockValueNode, blockPath))
        return nil
    }

    toMap, d := x.toMap(selefraBlockValueNode, blockPath)
    diagnostics.AddDiagnostics(d)
    if utils.HasError(d) {
        return nil
    }

    selefraBlock := module.NewSelefraBlock()
    for key, entry := range toMap {
        switch key {

        case SelefraBlockNameFieldName:
            selefraBlock.Name = x.parseStringValueWithDiagnosticsAndSetLocation(selefraBlock, SelefraBlockNameFieldName, entry, blockPath, diagnostics)

        // Omit some code
        default:
            diagnostics.AddDiagnostics(x.buildNodeErrorMsgForUnSupport(entry.key, entry.value, fmt.Sprintf("%s.%s", blockPath, key)))
        }
    }

    if selefraBlock.IsEmpty() {
        return nil

    }

    // Set code location
    x.setLocationKVWithDiagnostics(selefraBlock, "", blockPath, newNodeEntry(selefraBlockKeyNode, selefraBlockValueNode), diagnostics)

    return selefraBlock
}

Enter fullscreen mode Exit fullscreen mode

During the parsing of each data, we use predefined validation methods. Taking the parseStringValueWithDiagnosticsAndSetLocation method as an example:

func (x *YamlFileToModuleParser) parseStringValueWithDiagnosticsAndSetLocation(block module.Block, fieldName string, entry *nodeEntry, blockBasePath string, diagnostics *schema.Diagnostics) string {
    valueString := x.parseStringWithDiagnostics(entry.value, blockBasePath+"."+fieldName, diagnostics)

    if entry.key != nil {
        x.setLocationWithDiagnostics(block, fieldName+module.NodeLocationSelfKey, blockBasePath, entry.key, diagnostics)
    }

    x.setLocationWithDiagnostics(block, fieldName+module.NodeLocationSelfValue, blockBasePath, entry.value, diagnostics)

    return valueString
}

func (x *YamlFileToModuleParser) setLocationWithDiagnostics(block module.Block, relativeYamlSelectorPath, fullYamlSelectorPath string, node *yaml.Node, diagnostics *schema.Diagnostics) {
    location := module.BuildLocationFromYamlNode(x.yamlFilePath, fullYamlSelectorPath, node)
    err := block.SetNodeLocation(relativeYamlSelectorPath, location)
    if err != nil {
        diagnostics.AddErrorMsg("YamlFileToModuleParser error, build location for file %s %s error: %s", x.yamlFilePath, fullYamlSelectorPath, err.Error())
    }
}

Enter fullscreen mode Exit fullscreen mode

In this method, we validate the code value's validity and report any errors and modification suggestions to the user when it's invalid.

Complex Code Block: Module Block

In the Module block, we require additional method invocations for loading. We load sub-modules based on the uses field within the code block and establish associations using names and parent-child module pointers for subsequent code execution.

// Load sub-modules
subModuleSlice, loadSuccess := x.loadSubModules(ctx, finalModule.ModulesBlock)
if !loadSuccess {
    return nil, false
}
finalModule.SubModules = subModuleSlice
finalModule.Source = x.options.Source
finalModule.ModuleLocalDirectory = x.options.ModuleDirectory
finalModule.DependenciesPath = x.options.DependenciesTree

Enter fullscreen mode Exit fullscreen mode

These are the key operations involved in reading Selefra code. In the next article, we will explore how Selefra executes rules, queries non-compliant entries, and delivers the expected results to the user.

Thank you for reading!

Please follow our project and provide your star: https://github.com/selefra/selefra

Top comments (0)