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

Fix package validation on Windows #358

Merged
merged 11 commits into from
Jun 23, 2022
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
27 changes: 22 additions & 5 deletions .ci/Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,28 @@ pipeline {
Test the source code.
*/
stage('Test') {
steps {
cleanup()
withMageEnv(){
dir("${BASE_DIR}"){
sh(label: 'Test', script: 'make test')
parallel {
stage('Test on Linux') {
options { skipDefaultCheckout() }
steps {
jsoriano marked this conversation as resolved.
Show resolved Hide resolved
cleanup()
withMageEnv(){
dir("${BASE_DIR}"){
sh(label: 'Test', script: 'make test')
}
}
}
}
stage('Test on Windows') {
agent { label 'windows-10' }
options { skipDefaultCheckout() }
steps {
jsoriano marked this conversation as resolved.
Show resolved Hide resolved
cleanup()
withMageEnv(){
dir("${BASE_DIR}"){
bat(label: 'Test', script: 'go test -v ./code/go/...')
}
jsoriano marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions code/go/internal/fspath/fspath.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package fspath
import (
"io/fs"
"os"
"path/filepath"
"path"
)

// FS implements the fs interface and can also show a path where the fs is located.
Expand All @@ -26,7 +26,7 @@ type fsDir struct {

// Path returns a path for the given names, based on the location of the file system.
func (fs *fsDir) Path(names ...string) string {
return filepath.Join(append([]string{fs.path}, names...)...)
return path.Join(append([]string{fs.path}, names...)...)
}

// DirFS returns a file system for a directory, it keeps the path to implement the FS interface.
Expand Down
16 changes: 9 additions & 7 deletions code/go/internal/validator/folder_item_content.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
package validator

import (
"bytes"
"bufio"
"encoding/json"
"fmt"
"io/fs"
Expand Down Expand Up @@ -55,16 +55,18 @@ func validateYAMLDashes(fsys fs.FS, path string) error {
}
defer f.Close()

dashes := []byte("---\n")
b := make([]byte, len(dashes))
_, err = f.Read(b)
if err != nil {
scanner := bufio.NewScanner(f)
// A small buffer should be enough to check if the document starts with three dashes.
buf := make([]byte, 8)
scanner.Buffer(buf, len(buf))
scanner.Scan()
if err := scanner.Err(); err != bufio.ErrTooLong && err != nil {
return err
}

if !bytes.Equal(dashes, b) {
if scanner.Text() != "---" {
return errors.New("document dashes are required (start the document with '---')")
}

return nil
}

Expand Down
8 changes: 4 additions & 4 deletions code/go/internal/validator/folder_item_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package validator
import (
"fmt"
"io/fs"
"path/filepath"
"path"
"regexp"
"sync"

Expand Down Expand Up @@ -103,7 +103,7 @@ func (s *folderItemSpec) validateSchema(schemaFsys fs.FS, fsys fs.FS, folderSpec
return nil // item's schema is not defined
}

schemaPath := filepath.Join(filepath.Dir(folderSpecPath), s.Ref)
schemaPath := path.Join(path.Dir(folderSpecPath), s.Ref)
schemaLoader := yamlschema.NewReferenceLoaderFileSystem("file:///"+schemaPath, schemaFsys)

// validation with schema
Expand All @@ -120,8 +120,8 @@ func (s *folderItemSpec) validateSchema(schemaFsys fs.FS, fsys fs.FS, folderSpec
formatCheckersMutex.Unlock()
}()

semantic.LoadRelativePathFormatChecker(fsys, filepath.Dir(itemPath), s.Limits.RelativePathSizeLimit)
semantic.LoadDataStreamNameFormatChecker(fsys, filepath.Dir(itemPath))
semantic.LoadRelativePathFormatChecker(fsys, path.Dir(itemPath), s.Limits.RelativePathSizeLimit)
semantic.LoadDataStreamNameFormatChecker(fsys, path.Dir(itemPath))
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
if err != nil {
return ve.ValidationErrors{err}
Expand Down
44 changes: 22 additions & 22 deletions code/go/internal/validator/folder_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"io/fs"
"io/ioutil"
"log"
"path/filepath"
"path"
"regexp"
"strings"

Expand Down Expand Up @@ -71,32 +71,32 @@ func (s *folderSpec) load(fs fs.FS, specPath string) error {
return nil
}

func (s *folderSpec) validate(pkg *Package, path string) ve.ValidationErrors {
func (s *folderSpec) validate(pkg *Package, folderPath string) ve.ValidationErrors {
var errs ve.ValidationErrors
files, err := fs.ReadDir(pkg, path)
files, err := fs.ReadDir(pkg, folderPath)
if err != nil {
errs = append(errs, errors.Wrapf(err, "could not read folder [%s]", pkg.Path(path)))
errs = append(errs, errors.Wrapf(err, "could not read folder [%s]", pkg.Path(folderPath)))
return errs
}

// This is not taking into account if the folder is for development. Enforce
// this limit in all cases to avoid having to read too many files.
if contentsLimit := s.Limits.TotalContentsLimit; contentsLimit > 0 && len(files) > contentsLimit {
errs = append(errs, errors.Errorf("folder [%s] exceeds the limit of %d files", pkg.Path(path), contentsLimit))
errs = append(errs, errors.Errorf("folder [%s] exceeds the limit of %d files", pkg.Path(folderPath), contentsLimit))
return errs
}

// Don't enable beta features for packages marked as GA.
switch s.Release {
case "", "ga": // do nothing
case "beta":
if pkg.Version.Major() > 0 && pkg.Version.Prerelease() == "" {
errs = append(errs, errors.Errorf("spec for [%s] defines beta features which can't be enabled for packages with a stable semantic version", pkg.Path(path)))
} else {
log.Printf("Warning: package with non-stable semantic version and active beta features (enabled in [%s]) can't be released as stable version.", pkg.Path(path))
}
default:
errs = append(errs, errors.Errorf("unsupport release level, supported values: beta, ga"))
case "", "ga": // do nothing
case "beta":
if pkg.Version.Major() > 0 && pkg.Version.Prerelease() == "" {
errs = append(errs, errors.Errorf("spec for [%s] defines beta features which can't be enabled for packages with a stable semantic version", pkg.Path(folderPath)))
} else {
log.Printf("Warning: package with non-stable semantic version and active beta features (enabled in [%s]) can't be released as stable version.", pkg.Path(folderPath))
}
default:
errs = append(errs, errors.Errorf("unsupport release level, supported values: beta, ga"))
}

for _, file := range files {
Expand All @@ -113,15 +113,15 @@ func (s *folderSpec) validate(pkg *Package, path string) ve.ValidationErrors {
if !s.DevelopmentFolder && strings.Contains(fileName, "-") {
errs = append(errs,
fmt.Errorf(`file "%s" is invalid: directory name inside package %s contains -: %s`,
pkg.Path(path, fileName), pkg.Name, fileName))
pkg.Path(folderPath, fileName), pkg.Name, fileName))
}
}
continue
}

if itemSpec == nil && !s.AdditionalContents {
// No spec found for current folder item and we do not allow additional contents in folder.
errs = append(errs, fmt.Errorf("item [%s] is not allowed in folder [%s]", fileName, pkg.Path(path)))
errs = append(errs, fmt.Errorf("item [%s] is not allowed in folder [%s]", fileName, pkg.Path(folderPath)))
continue
}

Expand All @@ -145,7 +145,7 @@ func (s *folderSpec) validate(pkg *Package, path string) ve.ValidationErrors {
// Inherit limits from parent directory.
subFolderSpec.Limits = s.Limits
if itemSpec.Ref != "" {
subFolderSpecPath := filepath.Join(filepath.Dir(s.specPath), itemSpec.Ref)
subFolderSpecPath := path.Join(path.Dir(s.specPath), itemSpec.Ref)
err := subFolderSpec.load(s.fs, subFolderSpecPath)
if err != nil {
errs = append(errs, err)
Expand All @@ -163,7 +163,7 @@ func (s *folderSpec) validate(pkg *Package, path string) ve.ValidationErrors {
subFolderSpec.DevelopmentFolder = true
}

subFolderPath := filepath.Join(path, fileName)
subFolderPath := path.Join(folderPath, fileName)
subErrs := subFolderSpec.validate(pkg, subFolderPath)
if len(subErrs) > 0 {
errs = append(errs, subErrs...)
Expand All @@ -180,7 +180,7 @@ func (s *folderSpec) validate(pkg *Package, path string) ve.ValidationErrors {
continue
}

itemPath := filepath.Join(path, file.Name())
itemPath := path.Join(folderPath, file.Name())
itemValidationErrs := itemSpec.validate(s.fs, pkg, s.specPath, itemPath)
if itemValidationErrs != nil {
for _, ive := range itemValidationErrs {
Expand All @@ -199,7 +199,7 @@ func (s *folderSpec) validate(pkg *Package, path string) ve.ValidationErrors {
}

if sizeLimit := s.Limits.TotalSizeLimit; sizeLimit > 0 && s.totalSize > sizeLimit {
errs = append(errs, errors.Errorf("folder [%s] exceeds the total size limit of %s", pkg.Path(path), sizeLimit))
errs = append(errs, errors.Errorf("folder [%s] exceeds the total size limit of %s", pkg.Path(folderPath), sizeLimit))
}

// validate that required items in spec are all accounted for
Expand All @@ -217,9 +217,9 @@ func (s *folderSpec) validate(pkg *Package, path string) ve.ValidationErrors {
if !fileFound {
var err error
if itemSpec.Name != "" {
err = fmt.Errorf("expecting to find [%s] %s in folder [%s]", itemSpec.Name, itemSpec.ItemType, pkg.Path(path))
err = fmt.Errorf("expecting to find [%s] %s in folder [%s]", itemSpec.Name, itemSpec.ItemType, pkg.Path(folderPath))
} else if itemSpec.Pattern != "" {
err = fmt.Errorf("expecting to find %s matching pattern [%s] in folder [%s]", itemSpec.ItemType, itemSpec.Pattern, pkg.Path(path))
err = fmt.Errorf("expecting to find %s matching pattern [%s] in folder [%s]", itemSpec.ItemType, itemSpec.Pattern, pkg.Path(folderPath))
}
errs = append(errs, err)
}
Expand Down
4 changes: 2 additions & 2 deletions code/go/internal/validator/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"fmt"
"io/fs"
"os"
"path/filepath"
"path"

"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
Expand All @@ -33,7 +33,7 @@ func (p *Package) Open(name string) (fs.File, error) {

// Path returns a path meaningful for the user.
func (p *Package) Path(names ...string) string {
return filepath.Join(append([]string{p.location}, names...)...)
return path.Join(append([]string{p.location}, names...)...)
}

// NewPackage creates a new Package from a path to the package's root folder
Expand Down
1 change: 0 additions & 1 deletion code/go/internal/validator/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,3 @@ func TestNewPackage(t *testing.T) {
})
}
}

6 changes: 3 additions & 3 deletions code/go/internal/validator/semantic/format_checkers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package semantic

import (
"io/fs"
"path/filepath"
"path"

"github.com/xeipuuv/gojsonschema"

Expand Down Expand Up @@ -40,7 +40,7 @@ func (r relativePathChecker) IsFormat(input interface{}) bool {
return false
}

path := filepath.Join(r.currentPath, asString)
path := path.Join(r.currentPath, asString)
info, err := fs.Stat(r.fsys, path)
if err != nil {
return false
Expand Down Expand Up @@ -77,7 +77,7 @@ func UnloadRelativePathFormatChecker() {
func LoadDataStreamNameFormatChecker(fsys fs.FS, currentPath string) {
gojsonschema.FormatCheckers.Add(DataStreamNameFormat, relativePathChecker{
fsys: fsys,
currentPath: filepath.Join(currentPath, "data_stream"),
currentPath: path.Join(currentPath, "data_stream"),
})
}

Expand Down
17 changes: 8 additions & 9 deletions code/go/internal/validator/semantic/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
package semantic

import (
"fmt"
"io/fs"
"os"
"path/filepath"
"path"
"strings"

"github.com/pkg/errors"
Expand Down Expand Up @@ -84,7 +83,7 @@ func listFieldsFiles(fsys fspath.FS) ([]string, error) {
}

for _, dataStream := range dataStreams {
fieldsDir := filepath.Join(dataStreamDir, dataStream, "fields")
fieldsDir := path.Join(dataStreamDir, dataStream, "fields")
fs, err := fs.ReadDir(fsys, fieldsDir)
if errors.Is(err, os.ErrNotExist) {
continue
Expand All @@ -94,7 +93,7 @@ func listFieldsFiles(fsys fspath.FS) ([]string, error) {
}

for _, f := range fs {
fieldsFiles = append(fieldsFiles, filepath.Join(fieldsDir, f.Name()))
fieldsFiles = append(fieldsFiles, path.Join(fieldsDir, f.Name()))
}
}

Expand All @@ -116,13 +115,13 @@ func unmarshalFields(fsys fspath.FS, fieldsPath string) (fields, error) {
}

func dataStreamFromFieldsPath(pkgRoot, fieldsFile string) (string, error) {
dataStreamPath := filepath.Clean(filepath.Join(pkgRoot, "data_stream"))
relPath, err := filepath.Rel(dataStreamPath, filepath.Clean(fieldsFile))
if err != nil {
return "", fmt.Errorf("looking for fields file (%s) in data streams path (%s): %w", fieldsFile, dataStreamPath, err)
dataStreamPath := path.Clean(path.Join(pkgRoot, "data_stream")) + "/"
if !strings.HasPrefix(path.Clean(fieldsFile), dataStreamPath) {
return "", errors.Errorf("%q is not a fields file in a data stream of %q", fieldsFile, dataStreamPath)
}
relPath := strings.TrimPrefix(path.Clean(fieldsFile), dataStreamPath)

parts := strings.SplitN(relPath, string(filepath.Separator), 2)
parts := strings.SplitN(relPath, "/", 2)
if len(parts) != 2 {
return "", errors.Errorf("could not find data stream for fields file %s", fieldsFile)
}
Expand Down
54 changes: 54 additions & 0 deletions code/go/internal/validator/semantic/types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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 (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestDataStreamFromFieldsPath(t *testing.T) {
cases := []struct {
pkgRoot string
fieldsFile string
expected string
fail bool
}{
{
pkgRoot: "package",
fieldsFile: "package/data_stream/foo/fields/some-fields.yml",
expected: "foo",
},
{
pkgRoot: "package/",
fieldsFile: "package/data_stream/foo/fields/some-fields.yml",
expected: "foo",
},
{
pkgRoot: "/package/",
fieldsFile: "/package/data_stream/foo/fields/some-fields.yml",
expected: "foo",
},
{
pkgRoot: "/package/",
fieldsFile: "/package/fields/some-fields.yml",
fail: true,
},
}

for _, c := range cases {
t.Run(c.pkgRoot+"_"+c.fieldsFile, func(t *testing.T) {
dataStream, err := dataStreamFromFieldsPath(c.pkgRoot, c.fieldsFile)
if c.fail {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, c.expected, dataStream)
}
})
}
}
Loading