code/go/internal/specschema/folder_spec.go (102 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 specschema import ( "fmt" "io" "io/fs" "path" "github.com/Masterminds/semver/v3" "gopkg.in/yaml.v3" "github.com/elastic/package-spec/v3/code/go/internal/spectypes" ) // FolderSpecLoader loads specs from directories. type FolderSpecLoader struct { fs fs.FS fileSpecLoader spectypes.FileSchemaLoader specVersion semver.Version } // NewFolderSpecLoader creates a new `FolderSpecLoader` that loads schemas from the given directories. // File schemas referenced with `$ref` are loaded using the given `FileSchemaLoader`. func NewFolderSpecLoader(fs fs.FS, fileLoader spectypes.FileSchemaLoader, version semver.Version) *FolderSpecLoader { return &FolderSpecLoader{ fs: fs, fileSpecLoader: fileLoader, specVersion: version, } } // Load loads a spec from the given path. func (l *FolderSpecLoader) Load(specPath string) (*ItemSpec, error) { var spec folderItemSpec err := l.loadFolderSpec(&spec, path.Join(specPath, "spec.yml")) if err != nil { return nil, err } return &ItemSpec{&spec}, nil } func (l *FolderSpecLoader) loadFolderSpec(s *folderItemSpec, specPath string) error { specFile, err := l.fs.Open(specPath) if err != nil { return fmt.Errorf("could not open folder specification file: %w", err) } defer specFile.Close() data, err := io.ReadAll(specFile) if err != nil { return fmt.Errorf("could not read folder specification file: %w", err) } var folderSpec folderSchemaSpec folderSpec.Spec = s if err := yaml.Unmarshal(data, &folderSpec); err != nil { return fmt.Errorf("could not parse folder specification file: %w", err) } newSpec, err := folderSpec.resolve(l.specVersion) if err != nil { return err } err = l.loadContents(newSpec, l.fs, specPath) if err != nil { return err } err = newSpec.setDefaultValues() if err != nil { return fmt.Errorf("could not set default values: %w", err) } newSpec.propagateContentLimits() // it is required to assign the real values to be able to // use all the calculated contents in following iterations *s = *newSpec return nil } func (l *FolderSpecLoader) loadContents(s *folderItemSpec, fs fs.FS, specPath string) error { var content *folderItemSpec contents := append([]*folderItemSpec{}, s.Contents...) for len(contents) > 0 { content, contents = contents[0], contents[1:] // TODO: Visibility not used at the moment. if v := content.Visibility; v != "" && v != visibilityTypePrivate && v != visibilityTypePublic { return fmt.Errorf("item [%s] visibility is expected to be private or public, not [%s]", path.Join(specPath, content.Name), content.Visibility) } // All folders inside a development folder are too. if s.DevelopmentFolder { content.DevelopmentFolder = true } if content.Ref != "" { // Resolve references. switch content.ItemType { case spectypes.ItemTypeFile: if l.fileSpecLoader == nil { break } specPath := path.Join(path.Dir(specPath), content.Ref) options := spectypes.FileSchemaLoadOptions{ SpecVersion: l.specVersion, Limits: &ItemSpec{content}, ContentType: content.ContentMediaType, } schema, err := l.fileSpecLoader.Load(fs, specPath, options) if err != nil { return fmt.Errorf("could not load schema for %q: %w", path.Dir(specPath), err) } content.schema = schema case spectypes.ItemTypeFolder: p := path.Join(path.Dir(specPath), content.Ref) err := l.loadFolderSpec(content, p) if err != nil { return fmt.Errorf("could not load spec for %q: %w", p, err) } } } else if len(content.Contents) > 0 { // Walk over folders defined inline. contents = append(contents, content.Contents...) } } return nil }