code/go/internal/validator/semantic/validate_minimum_kibana_version.go (138 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 ( "fmt" "regexp" "github.com/Masterminds/semver/v3" "github.com/elastic/package-spec/v3/code/go/internal/fspath" "github.com/elastic/package-spec/v3/code/go/internal/packages" "github.com/elastic/package-spec/v3/code/go/internal/pkgpath" "github.com/elastic/package-spec/v3/code/go/pkg/specerrors" ) // ValidateMinimumKibanaVersion ensures the minimum kibana version for a given package is the expected one func ValidateMinimumKibanaVersion(fsys fspath.FS) specerrors.ValidationErrors { pkg, err := packages.NewPackageFromFS(fsys.Path(), fsys) if err != nil { return specerrors.ValidationErrors{specerrors.NewStructuredError(err, specerrors.UnassignedCode)} } manifest, err := readManifest(fsys) if err != nil { return specerrors.ValidationErrors{specerrors.NewStructuredError(err, specerrors.UnassignedCode)} } kibanaVersionCondition, err := getKibanaVersionCondition(*manifest) if err != nil { return specerrors.ValidationErrors{specerrors.NewStructuredError(err, specerrors.UnassignedCode)} } var errs specerrors.ValidationErrors err = validateMinimumKibanaVersionInputPackages(pkg.Type, *pkg.Version, kibanaVersionCondition) if err != nil { errs = append(errs, specerrors.NewStructuredError(err, specerrors.UnassignedCode)) } err = validateMinimumKibanaVersionRuntimeFields(fsys, *pkg.Version, kibanaVersionCondition) if err != nil { errs = append(errs, specerrors.NewStructuredError(err, specerrors.UnassignedCode)) } err = validateMinimumKibanaVersionSavedObjectTags(fsys, pkg.Type, *pkg.Version, kibanaVersionCondition) if err != nil { errs = append(errs, specerrors.NewStructuredError(err, specerrors.CodeMinimumKibanaVersion)) } if errs != nil { return errs } return nil } // validateMinimumKibanaVersionInputPackages ensures the minimum kibana version if the package is an input package, and the package version is >= 1.0.0, // then the kibana version condition for the package must be >= 8.8.0 func validateMinimumKibanaVersionInputPackages(packageType string, packageVersion semver.Version, kibanaVersionCondition string) error { const minimumKibanaVersion = "8.8.0" if packageType != "input" { return nil } if packageVersion.LessThan(semver.MustParse("1.0.0")) { return nil } if kibanaVersionConditionIsGreaterThanOrEqualTo(kibanaVersionCondition, minimumKibanaVersion) { return nil } return fmt.Errorf("conditions.kibana.version must be ^%s or greater for non experimental input packages (version > 1.0.0)", minimumKibanaVersion) } // validateMinimumKibanaVersionRuntimeFields ensures the minimum kibana version if the package defines any runtime field, // then the kibana version condition for the package must be >= 8.10.0 func validateMinimumKibanaVersionRuntimeFields(fsys fspath.FS, packageVersion semver.Version, kibanaVersionCondition string) error { const minimumKibanaVersion = "8.10.0" errs := validateFields(fsys, validateNoRuntimeFields) if len(errs) == 0 { return nil } if kibanaVersionConditionIsGreaterThanOrEqualTo(kibanaVersionCondition, minimumKibanaVersion) { return nil } return fmt.Errorf("conditions.kibana.version must be ^%s or greater to include runtime fields", minimumKibanaVersion) } // validateMinimumKibanaVersionSavedObjectTags ensures the minimum kibana version if the package defines saved object tags file, // then the kibana version condition for the package must be >= 8.10.0 func validateMinimumKibanaVersionSavedObjectTags(fsys fspath.FS, packageType string, packageVersion semver.Version, kibanaVersionCondition string) error { const minimumKibanaVersion = "8.10.0" if packageType == "input" { return nil } manifestPath := "kibana/tags.yml" f, err := pkgpath.Files(fsys, manifestPath) if err != nil { return fmt.Errorf("can't locate files with %v: %w", manifestPath, err) } if len(f) == 0 { return nil } if kibanaVersionConditionIsGreaterThanOrEqualTo(kibanaVersionCondition, minimumKibanaVersion) { return nil } return fmt.Errorf("conditions.kibana.version must be ^%s or greater to include saved object tags file: %s", minimumKibanaVersion, manifestPath) } func readManifest(fsys fspath.FS) (*pkgpath.File, error) { manifestPath := "manifest.yml" f, err := pkgpath.Files(fsys, manifestPath) if err != nil { return nil, fmt.Errorf("can't locate manifest file: %w", err) } if len(f) != 1 { return nil, fmt.Errorf("single manifest file expected") } return &f[0], nil } func getKibanaVersionCondition(manifest pkgpath.File) (string, error) { val, err := manifest.Values("$.conditions[\"kibana.version\"]") if err != nil { val, err = manifest.Values("$.conditions.kibana.version") if err != nil { return "", nil } } sVal, ok := val.(string) if !ok { return "", fmt.Errorf("manifest kibana version is not a string") } return sVal, nil } func kibanaVersionConditionIsGreaterThanOrEqualTo(kibanaVersionCondition, minimumVersion string) bool { if kibanaVersionCondition == "" { return false } if kibanaVersionCondition == fmt.Sprintf("^%s", minimumVersion) { return true } // get all versions e.g 8.8.0, 8.8.1 from "^8.8.0 || ^8.8.1" and check if any of them is less than 8.8.0 pattern := `(\d+\.\d+\.\d+)` minSemver := semver.MustParse(minimumVersion) regex := regexp.MustCompile(pattern) matches := regex.FindAllString(kibanaVersionCondition, -1) for _, match := range matches { matchVersion, err := semver.NewVersion(match) if err != nil { return false } if matchVersion.LessThan(minSemver) { return false } } return true } func validateNoRuntimeFields(metadata fieldFileMetadata, f field) specerrors.ValidationErrors { if f.Runtime.isEnabled() { return specerrors.ValidationErrors{ specerrors.NewStructuredErrorf("%v file contains a field %s with runtime key defined (%s)", metadata.fullFilePath, f.Name, f.Runtime), } } return nil }