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

feat: add support for pkl files #164

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ jobs:
with:
go-version: '1.22'

- name: Set up pkl
uses: pkl-community/setup-pkl@5bb6ac805eb51c448837ec34e9957a18adab927d # v0.0.5
with:
pkl-version: '0.26.3'

- name: Unit test
run: go test -v -cover -coverprofile coverage.out ./...

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
golangci:
strategy:
matrix:
go: ['1.21']
go: ['1.22']
os: [ubuntu-latest, macos-latest, windows-latest]
permissions:
# Optional: Allow write access to checks to allow the action to annotate code in the PR.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
* INI
* JSON
* Properties
* PKL _(requires that the `pkl` binary is installed; `.pkl` files will be ignored if the binary is not installed)_
* TOML
* XML
* YAML
Expand Down Expand Up @@ -143,7 +144,7 @@ validator --exclude-dirs=/path/to/search/tests /path/to/search
![Exclude Dirs Run](./img/exclude_dirs.png)

#### Exclude file types
Exclude file types in the search path. Available file types are `csv`, `env`, `hcl`, `hocon`, `ini`, `json`, `plist`, `properties`, `toml`, `xml`, `yaml`, and `yml`
Exclude file types in the search path. Available file types are `csv`, `env`, `hcl`, `hocon`, `ini`, `json`, `plist`, `properties`, `pkl`, `toml`, `xml`, `yaml`, and `yml`

```
validator --exclude-file-types=json /path/to/search
Expand Down Expand Up @@ -207,7 +208,7 @@ docker run -it --rm -v /path/to/config/files:/test config-file-validator:1.6.0 /
![Docker Standard Run](./img/docker_run.png)

## Build
The project can be downloaded and built from source using an environment with Go 1.21+ installed. After a successful build, the binary can be moved to a location on your operating system PATH.
The project can be downloaded and built from source using an environment with Go 1.22.1+ installed. After a successful build, the binary can be moved to a location on your operating system PATH.

### macOS
#### Build
Expand Down
2 changes: 1 addition & 1 deletion cmd/validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Validator recursively scans a directory to search for configuration files and
validates them using the go package for each configuration type.

Currently Apple PList XML, CSV, HCL, HOCON, INI, JSON, Properties, TOML, XML, and YAML.
Currently Apple PList XML, PKL, CSV, HCL, HOCON, INI, JSON, Properties, TOML, XML, and YAML.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alphabetize please

configuration file types are supported.

Usage: validator [OPTIONS] [<search_path>...]
Expand Down
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
module github.com/Boeing/config-file-validator

go 1.21
go 1.22.1

require (
github.com/fatih/color v1.13.0
github.com/gurkankaymak/hocon v1.2.18
github.com/hashicorp/hcl/v2 v2.18.1
github.com/magiconair/properties v1.8.7
github.com/pelletier/go-toml/v2 v2.0.6
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.9.0
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.0
Expand All @@ -18,6 +18,7 @@ require (
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/apple/pkl-go v0.8.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
Expand All @@ -26,6 +27,8 @@ require (
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/zclconf/go-cty v1.13.0 // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/sync v0.2.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/apple/pkl-go v0.8.0 h1:GRcBvFWeXjT9rc7A5gHK89qrel2wGZ3/a7ge4rPlT5M=
github.com/apple/pkl-go v0.8.0/go.mod h1:5Hwil5tyZGrOekh7JXLZJvIAcGHb4gT19lnv4WEiKeI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -49,6 +51,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0=
github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
Expand Down
5 changes: 3 additions & 2 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
* INI
* JSON
* Properties
* PKL _(requires that the `pkl` binary is installed; `.pkl` files will be ignored if the binary is not installed)_
* TOML
* XML
* YAML
Expand Down Expand Up @@ -157,7 +158,7 @@ validator --exclude-dirs=/path/to/search/tests /path/to/search
![Exclude Dirs Run](./img/exclude_dirs.png)

#### Exclude file types
Exclude file types in the search path. Available file types are `csv`, `env`, `hcl`, `hocon`, `ini`, `json`, `plist`, `properties`, `toml`, `xml`, `yaml`, and `yml`
Exclude file types in the search path. Available file types are `csv`, `env`, `hcl`, `hocon`, `ini`, `json`, `plist`, `properties`, `pkl`, `toml`, `xml`, `yaml`, and `yml`

```
validator --exclude-file-types=json /path/to/search
Expand Down Expand Up @@ -221,7 +222,7 @@ docker run -it --rm -v /path/to/config/files:/test config-file-validator:1.6.0 /
![Docker Standard Run](./img/docker_run.png)

## Build
The project can be downloaded and built from source using an environment with Go 1.21+ installed. After a successful build, the binary can be moved to a location on your operating system PATH.
The project can be downloaded and built from source using an environment with Go 1.22.1+ installed. After a successful build, the binary can be moved to a location on your operating system PATH.

### macOS
#### Build
Expand Down
15 changes: 15 additions & 0 deletions pkg/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"fmt"
"os"
"os/exec"

"github.com/Boeing/config-file-validator/pkg/finder"
"github.com/Boeing/config-file-validator/pkg/reporter"
Expand Down Expand Up @@ -73,6 +74,12 @@ func Init(opts ...Option) *CLI {
return cli
}

// Check if the 'pkl' binary is present in the PATH
var isPklBinaryPresent = func() bool {
_, err := exec.LookPath("pkl")
return err == nil
}
Comment on lines +77 to +81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this as a method of pkl validator

This would help you to validate it's behavior, abdband would prevent race in tests

Copy link
Author

@olunusib olunusib Sep 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I briefly contemplated this but decided against it for 2 reasons:

  • The intended behavior is to skip the files entirely. If we handle this within the pkl validator, we'd have to return a true or false value, which implies that we've performed the validation and determined whether the file is good or bad. This feels like a breach of contract IMO and it goes against our intention of skipping the files altogether.
  • We would lose the ability to log the exact files that were skipped since validate only takes the content of the files, not the file paths

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the second point, it would need to rewrite a part of the code

About the idea of perf, I don't mind here. If there is a pkl file, going to the pkl code to see if the file is valid, would require to validate its content. So it would be slow. Of course, when the pkl binary is not present, the code might have to go through the code and finally do not execute the code

Please note you could consider simulating the binary presence, error, absence by calling arbitrary binary name:

  • true, always return 0
  • false, always return 1
  • a random string, the binary will be absent

It would be better than overloading the isPklBinaryPresent by something else

I'm unsure it's a good idea, but why not, it may help

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @ccoVeille and feel like the validator should be able to validate if its enabled or not rather than having the check in the CLI. The validator will see the file, realize that it is not set up properly (no PKL) and then skip it and provide that feedback to the report.


// The Run method performs the following actions:
// - Finds the calls the Find method from the Finder interface to
// return a list of files
Expand All @@ -88,6 +95,14 @@ func (c CLI) Run() (int, error) {
}

for _, fileToValidate := range foundFiles {
// Pkl validation requires the 'pkl' binary to be present
if fileToValidate.FileType.Name == "pkl" {
if !isPklBinaryPresent() {
fmt.Printf("Warning: 'pkl' binary not found, file %s will be ignored.\n", fileToValidate.Path)
continue
}
}

Comment on lines +98 to +105
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this in the pkl validator

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same response as below

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go back to the reason I raised the point of doing something else that what is in this PR

I see a problem in having a loop, then adding a dirty if hack in the loop.

The validator should be in charge of validating, it's not the loop that should do a big if.

// read it
fileContent, err := os.ReadFile(fileToValidate.Path)
if err != nil {
Expand Down
27 changes: 27 additions & 0 deletions pkg/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,30 @@ func Test_CLIRepoertErr(t *testing.T) {
t.Errorf("should return err status code: %d", exitStatus)
}
}

func Test_CLI_IgnoreBadPklFileWhenBinaryNotFound(t *testing.T) {
// Save the original function before mocking and restore it after the test
originalIsPklBinaryPresent := isPklBinaryPresent
isPklBinaryPresent = func() bool {
return false
}
defer func() { isPklBinaryPresent = originalIsPklBinaryPresent }()
Comment on lines +126 to +131
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid this could lead to data race, when other tests are running

Using a lock mechanism to solve this would be over complicated as it would be applied even with the CLI

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests within the same package run sequentially, so there shouldn't be a risk of a data race here. Lmk if that's not the case.

If we decide to make these parallel later, I think adding locking wouldn't be a bad idea. Can you explain what you mean by "it would be applied even with the CLI"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The locking I was suggesting would have been a mutex.Read/Write + defer unlock

to use it in the tests, I think it would require to use it in the code too, so when trying to check if isPklBinaryPresent here, when using it in the CLI, the lock would have to be present

The test are ran sequentially, but using a -race help you to spot such issues.


searchPath := "../../test/fixtures/subdir2/bad.pkl"
fsFinder := finder.FileSystemFinderInit(
finder.WithPathRoots(searchPath),
)
cli := Init(
WithFinder(fsFinder),
)
exitStatus, err := cli.Run()
if err != nil {
t.Errorf("An error was returned: %v", err)
}

// Since the pkl binary is not found, the bad pkl file should be ignored
// So the exit status should be 0
if exitStatus != 0 {
t.Errorf("Expected exit status 0, but got: %d", exitStatus)
}
}
9 changes: 9 additions & 0 deletions pkg/filetype/file_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ var PropFileType = FileType{
validator.PropValidator{},
}

// Instance of the FileType object to
// represent a Pkl file
var PklFileType = FileType{
"pkl",
misc.ArrToMap("pkl"),
validator.PklValidator{},
}

// Instance of the FileType object to
// represent a HCL file
var HclFileType = FileType{
Expand Down Expand Up @@ -120,6 +128,7 @@ var FileTypes = []FileType{
TomlFileType,
IniFileType,
PropFileType,
PklFileType,
HclFileType,
PlistFileType,
CsvFileType,
Expand Down
32 changes: 32 additions & 0 deletions pkg/validator/pkl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package validator

import (
"context"
"fmt"

"github.com/apple/pkl-go/pkl"
)

// PklValidator is used to validate a byte slice that is intended to represent a
// PKL file.
type PklValidator struct{}

// Validate attempts to evaluate the provided byte slice as a PKL file.
func (PklValidator) Validate(b []byte) (bool, error) {
ctx := context.Background()

// Convert the byte slice to a ModuleSource using TextSource
source := pkl.TextSource(string(b))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there conditions where pkl.TextSource will return an err? Do we need to handle the err?


evaluator, err := pkl.NewEvaluator(ctx, pkl.PreconfiguredOptions)
if err != nil {
return false, fmt.Errorf("failed to create evaluator: %w", err)
}

_, err = evaluator.EvaluateExpressionRaw(ctx, source, "")
if err != nil {
return false, fmt.Errorf("failed to evaluate module: %w", err)
}

return true, nil
}
15 changes: 15 additions & 0 deletions pkg/validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package validator

import (
_ "embed"
"os/exec"
"testing"
)

Expand Down Expand Up @@ -55,6 +56,8 @@ var testData = []struct {
{"invalidIni", []byte(`\nCatalog hidden\n`), false, IniValidator{}},
{"validProperties", []byte("key=value\nkey2=${key}"), true, PropValidator{}},
{"invalidProperties", []byte("key=${key}"), false, PropValidator{}},
{"validPkl", []byte(`name = "Swallow"`), true, PklValidator{}},
{"invalidPkl", []byte(`"name" = "Swallow"`), false, PklValidator{}},
{"validHcl", []byte(`key = "value"`), true, HclValidator{}},
{"invalidHcl", []byte(`"key" = "value"`), false, HclValidator{}},
{"multipleInvalidHcl", []byte(`"key1" = "value1"\n"key2"="value2"`), false, HclValidator{}},
Expand All @@ -70,6 +73,11 @@ var testData = []struct {
{"invalidEditorConfig", []byte("[*.md\nworking=false"), false, EditorConfigValidator{}},
}

func isPklBinaryPresent() bool {
_, err := exec.LookPath("pkl")
return err == nil
}

func Test_ValidationInput(t *testing.T) {
t.Parallel()

Expand All @@ -79,6 +87,13 @@ func Test_ValidationInput(t *testing.T) {
t.Run(tcase.name, func(t *testing.T) {
t.Parallel()

// Skip PklValidator tests if the pkl binary is not present
if _, ok := tcase.validator.(PklValidator); ok {
if !isPklBinaryPresent() {
t.Skip("Skipping test: 'pkl' binary not found.")
}
}

valid, err := tcase.validator.Validate(tcase.testInput)
if valid != tcase.expectedResult {
t.Errorf("incorrect result: expected %v, got %v", tcase.expectedResult, valid)
Expand Down
7 changes: 7 additions & 0 deletions test/fixtures/good.pkl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name = "Swallow"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would prefer a more complete example


job {
title = "Sr. Nest Maker"
company = "Nests R Us"
yearsOfExperience = 2
}
1 change: 1 addition & 0 deletions test/fixtures/subdir2/bad.pkl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"invalid"
Loading