code/go/internal/validator/semantic/validate_required_vargroups.go (121 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. package semantic import ( "io/fs" "path" "slices" "github.com/elastic/package-spec/v3/code/go/internal/fspath" "github.com/elastic/package-spec/v3/code/go/pkg/specerrors" "gopkg.in/yaml.v3" ) // ValidateRequiredVarGroups validates lists of optional required variables. func ValidateRequiredVarGroups(fsys fspath.FS) specerrors.ValidationErrors { // Validate main manifest. d, err := fs.ReadFile(fsys, "manifest.yml") if err != nil { return specerrors.ValidationErrors{specerrors.NewStructuredErrorf("file \"%s\" is invalid: failed to read manifest: %w", fsys.Path("manifest.yml"), err)} } var manifest requiredVarsManifest err = yaml.Unmarshal(d, &manifest) if err != nil { return specerrors.ValidationErrors{specerrors.NewStructuredErrorf("file \"%s\" is invalid: failed to parse manifest: %w", fsys.Path("manifest.yml"), err)} } errs := validateRequiredVarGroupsManifest(fsys.Path("manifest.yml"), manifest) // Validate data stream manifests. dataStreams, err := listDataStreams(fsys) if err != nil { return specerrors.ValidationErrors{specerrors.NewStructuredErrorf("failed to list data streams: %w", err)} } for _, ds := range dataStreams { errs = append(errs, validateDataStreamRequiredVarGroups(fsys, path.Join("data_stream", ds, "manifest.yml"), manifest)...) } return errs } type requiredVarsManifestVar struct { Name string `yaml:"name"` Required bool `yaml:"required"` } type requiredVarsManifest struct { Vars []requiredVarsManifestVar `yaml:"vars"` PolicyTemplates []struct { Vars []requiredVarsManifestVar `yaml:"vars"` Inputs []struct { Type string `yaml:"type"` Vars []requiredVarsManifestVar `yaml:"vars"` RequiredVars map[string][]requiredVarsManifestVar `yaml:"required_vars"` } `yaml:"inputs"` } `yaml:"policy_templates"` } func (m requiredVarsManifest) findInputVars(inputType string) []requiredVarsManifestVar { for _, template := range m.PolicyTemplates { for _, input := range template.Inputs { if input.Type == inputType { return input.Vars } } } return nil } func validateRequiredVarGroupsManifest(path string, manifest requiredVarsManifest) specerrors.ValidationErrors { var errs specerrors.ValidationErrors for _, template := range manifest.PolicyTemplates { var vars []requiredVarsManifestVar vars = append(vars, manifest.Vars...) vars = append(vars, template.Vars...) for _, input := range template.Inputs { vars := append(slices.Clone(vars), input.Vars...) for _, varGroup := range input.RequiredVars { errs = append(errs, validateRequiredVarsDefined(path, vars, varGroup)...) } } } return errs } func validateDataStreamRequiredVarGroups(fsys fspath.FS, path string, pkgManifest requiredVarsManifest) specerrors.ValidationErrors { d, err := fs.ReadFile(fsys, path) if err != nil { return specerrors.ValidationErrors{specerrors.NewStructuredErrorf("file \"%s\" is invalid: failed to read manifest: %w", fsys.Path(path), err)} } var manifest requiredVarsDataStreamManifest err = yaml.Unmarshal(d, &manifest) if err != nil { return specerrors.ValidationErrors{specerrors.NewStructuredErrorf("file \"%s\" is invalid: failed to parse manifest: %w", fsys.Path(path), err)} } return validateDataStreamRequiredVarGroupsManifest(fsys.Path(path), manifest, pkgManifest) } type requiredVarsDataStreamManifest struct { Streams []struct { Input string `yaml:"input"` Vars []requiredVarsManifestVar `yaml:"vars"` RequiredVars map[string][]requiredVarsManifestVar `yaml:"required_vars"` } `yaml:"streams"` } func validateDataStreamRequiredVarGroupsManifest(path string, manifest requiredVarsDataStreamManifest, pkgManifest requiredVarsManifest) specerrors.ValidationErrors { var errs specerrors.ValidationErrors for _, stream := range manifest.Streams { vars := slices.Clone(stream.Vars) vars = append(vars, pkgManifest.Vars...) vars = append(vars, pkgManifest.findInputVars(stream.Input)...) for _, varGroup := range stream.RequiredVars { errs = append(errs, validateRequiredVarsDefined(path, vars, varGroup)...) } } return errs } func validateRequiredVarsDefined(path string, vars, requiredVars []requiredVarsManifestVar) specerrors.ValidationErrors { var errs specerrors.ValidationErrors for _, requiredVar := range requiredVars { if requiredVar.Name == "" { continue } i := slices.IndexFunc(vars, func(v requiredVarsManifestVar) bool { return requiredVar.Name == v.Name }) if i < 0 { errs = append(errs, specerrors.NewStructuredErrorf("file \"%s\" is invalid: required var %q in optional group is not defined", path, requiredVar.Name)) continue } if vars[i].Required { errs = append(errs, specerrors.NewStructuredErrorf("file \"%s\" is invalid: required var %q in optional group is defined as always required", path, requiredVar.Name)) } } return errs }