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

Make the parser able to parse any file (including files with .yml extension) #186

Merged
merged 9 commits into from
Nov 2, 2023
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
## About

The Devfile Parser library is a Golang module that:
1. parses the devfile.yaml as specified by the [api](https://devfile.github.io/devfile/api-reference.html) & [schema](https://github.com/devfile/api/tree/main/schemas/latest).
2. writes to the devfile.yaml with the updated data.
1. parses a devfile as specified by the [api](https://devfile.io/docs/2.2.1/devfile-schema) & [schema](https://github.com/devfile/api/tree/main/schemas/latest).
2. writes to the specified devfile with the updated data.
3. generates Kubernetes objects for the various devfile resources.
4. defines util functions for the devfile.
5. downloads resources from a parent devfile if specified in the devfile.yaml
5. downloads resources from a parent devfile if specified in the devfile.

## Private repository support

Expand Down
235 changes: 234 additions & 1 deletion pkg/devfile/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package devfile

import (
"context"
"fmt"
"net"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -273,6 +274,7 @@ spec:
wantKubernetesInline string
wantOpenshiftInline string
wantVariables map[string]string
additionalChecks func(parser.DevfileObj) error
}{
{
name: "with external overriding variables",
Expand Down Expand Up @@ -415,7 +417,7 @@ spec:
ExternalVariables: map[string]string{
"PARAMS": "baz",
},
Path: "./testdata/devfile1.yaml",
Path: "./testdata/devfile.yaml",
},
},
wantCommandLine: "./main baz",
Expand Down Expand Up @@ -527,6 +529,230 @@ spec:
StarterProjects: map[string][]string{},
},
},
{
rm3l marked this conversation as resolved.
Show resolved Hide resolved
name: "parsing devfile with context path containing multiple devfiles => priority to devfile.yaml",
args: args{
args: parser.ParserArgs{
ExternalVariables: map[string]string{
"PARAMS": "from devfile.yaml based on priority",
},
Path: "./testdata",
},
},
wantCommandLine: "./main from devfile.yaml based on priority",
wantVariables: map[string]string{
"PARAMS": "from devfile.yaml based on priority",
},
wantVarWarning: variables.VariableWarning{
Commands: map[string][]string{},
Components: map[string][]string{},
Projects: map[string][]string{},
StarterProjects: map[string][]string{},
},
additionalChecks: func(devfileObj parser.DevfileObj) error {
if devfileObj.Data.GetMetadata().DisplayName != "Go Runtime (devfile.yaml)" {
return fmt.Errorf("expected 'Go Runtime (devfile.yaml)' as metadata.displayName in devfile, but got %q",
devfileObj.Data.GetMetadata().DisplayName)
}
return nil
},
},
{
name: "parsing devfile with context path containing multiple devfiles => priority to .devfile.yaml",
args: args{
args: parser.ParserArgs{
ExternalVariables: map[string]string{
"PARAMS": "from .devfile.yaml based on priority",
},
Path: "./testdata/priority-for-dot_devfile_yaml",
},
},
wantCommandLine: "./main from .devfile.yaml based on priority",
wantVariables: map[string]string{
"PARAMS": "from .devfile.yaml based on priority",
},
wantVarWarning: variables.VariableWarning{
Commands: map[string][]string{},
Components: map[string][]string{},
Projects: map[string][]string{},
StarterProjects: map[string][]string{},
},
additionalChecks: func(devfileObj parser.DevfileObj) error {
if devfileObj.Data.GetMetadata().DisplayName != "Go Runtime (.devfile.yaml)" {
return fmt.Errorf("expected 'Go Runtime (.devfile.yaml)' as metadata.displayName in devfile, but got %q",
devfileObj.Data.GetMetadata().DisplayName)
}
return nil
},
},
{
name: "parsing devfile with context path containing multiple devfiles => priority to devfile.yml",
args: args{
args: parser.ParserArgs{
ExternalVariables: map[string]string{
"PARAMS": "from devfile.yml based on priority",
},
Path: "./testdata/priority-for-devfile_yml",
},
},
wantCommandLine: "./main from devfile.yml based on priority",
wantVariables: map[string]string{
"PARAMS": "from devfile.yml based on priority",
},
wantVarWarning: variables.VariableWarning{
Commands: map[string][]string{},
Components: map[string][]string{},
Projects: map[string][]string{},
StarterProjects: map[string][]string{},
},
additionalChecks: func(devfileObj parser.DevfileObj) error {
if devfileObj.Data.GetMetadata().DisplayName != "Test stack (devfile.yml)" {
return fmt.Errorf("expected 'Test stack (devfile.yml)' as metadata.displayName in devfile, but got %q",
devfileObj.Data.GetMetadata().DisplayName)
}
return nil
},
},
{
name: "parsing devfile with context path containing multiple devfiles => priority to .devfile.yml",
args: args{
args: parser.ParserArgs{
ExternalVariables: map[string]string{
"PARAMS": "from .devfile.yml based on priority",
},
Path: "./testdata/priority-for-dot_devfile_yml",
},
},
wantCommandLine: "./main from .devfile.yml based on priority",
wantVariables: map[string]string{
"PARAMS": "from .devfile.yml based on priority",
},
wantVarWarning: variables.VariableWarning{
Commands: map[string][]string{},
Components: map[string][]string{},
Projects: map[string][]string{},
StarterProjects: map[string][]string{},
},
additionalChecks: func(devfileObj parser.DevfileObj) error {
if devfileObj.Data.GetMetadata().DisplayName != "Test stack (.devfile.yml)" {
return fmt.Errorf("expected 'Test stack (.devfile.yml)' as metadata.displayName in devfile, but got %q",
devfileObj.Data.GetMetadata().DisplayName)
}
return nil
},
},
{
name: "parsing devfile with .yml extension",
args: args{
args: parser.ParserArgs{
ExternalVariables: map[string]string{
"PARAMS": "from devfile.yml",
},
Path: "./testdata/devfile.yml",
},
},
wantCommandLine: "./main from devfile.yml",
wantVariables: map[string]string{
"PARAMS": "from devfile.yml",
},
wantVarWarning: variables.VariableWarning{
Commands: map[string][]string{},
Components: map[string][]string{},
Projects: map[string][]string{},
StarterProjects: map[string][]string{},
},
additionalChecks: func(devfileObj parser.DevfileObj) error {
if devfileObj.Data.GetMetadata().DisplayName != "Test stack (devfile.yml)" {
return fmt.Errorf("expected 'Test stack (devfile.yml)' as metadata.displayName in devfile, but got %q",
devfileObj.Data.GetMetadata().DisplayName)
}
return nil
},
},
{
name: "parsing .devfile with .yml extension",
args: args{
args: parser.ParserArgs{
ExternalVariables: map[string]string{
"PARAMS": "from .devfile.yml",
},
Path: "./testdata/.devfile.yml",
},
},
wantCommandLine: "./main from .devfile.yml",
wantVariables: map[string]string{
"PARAMS": "from .devfile.yml",
},
wantVarWarning: variables.VariableWarning{
Commands: map[string][]string{},
Components: map[string][]string{},
Projects: map[string][]string{},
StarterProjects: map[string][]string{},
},
additionalChecks: func(devfileObj parser.DevfileObj) error {
if devfileObj.Data.GetMetadata().DisplayName != "Test stack (.devfile.yml)" {
return fmt.Errorf("expected 'Test stack (.devfile.yml)' as metadata.displayName in devfile, but got %q",
devfileObj.Data.GetMetadata().DisplayName)
}
return nil
},
},
{
name: "parsing .devfile with .yaml extension",
args: args{
args: parser.ParserArgs{
ExternalVariables: map[string]string{
"PARAMS": "from .devfile.yaml",
},
Path: "./testdata/.devfile.yaml",
},
},
wantCommandLine: "./main from .devfile.yaml",
wantVariables: map[string]string{
"PARAMS": "from .devfile.yaml",
},
wantVarWarning: variables.VariableWarning{
Commands: map[string][]string{},
Components: map[string][]string{},
Projects: map[string][]string{},
StarterProjects: map[string][]string{},
},
additionalChecks: func(devfileObj parser.DevfileObj) error {
if devfileObj.Data.GetMetadata().DisplayName != "Go Runtime (.devfile.yaml)" {
return fmt.Errorf("expected 'Go Runtime (.devfile.yaml)' as metadata.displayName in devfile, but got %q",
devfileObj.Data.GetMetadata().DisplayName)
}
return nil
},
},
{
name: "parsing any valid devfile regardless of extension",
args: args{
args: parser.ParserArgs{
ExternalVariables: map[string]string{
"PARAMS": "from any valid devfile file",
},
Path: "./testdata/valid-devfile.yaml.txt",
},
},
wantCommandLine: "./main from any valid devfile file",
wantVariables: map[string]string{
"PARAMS": "from any valid devfile file",
},
wantVarWarning: variables.VariableWarning{
Commands: map[string][]string{},
Components: map[string][]string{},
Projects: map[string][]string{},
StarterProjects: map[string][]string{},
},
additionalChecks: func(devfileObj parser.DevfileObj) error {
if devfileObj.Data.GetMetadata().DisplayName != "Test stack (valid-devfile.yaml.txt)" {
return fmt.Errorf("expected 'Test stack (valid-devfile.yaml.txt)' as metadata.displayName in devfile, but got %q",
devfileObj.Data.GetMetadata().DisplayName)
}
return nil
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -647,6 +873,13 @@ spec:
if !reflect.DeepEqual(variables, tt.wantVariables) {
t.Errorf("variables are %+v, expected %+v", variables, tt.wantVariables)
}

if tt.additionalChecks != nil {
err = tt.additionalChecks(gotD)
if err != nil {
t.Errorf("unexpected error while performing specific checks: %v", err)
}
}
})
}
}
26 changes: 9 additions & 17 deletions pkg/devfile/parser/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@
package parser

import (
"fmt"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/devfile/library/v2/pkg/testingutil/filesystem"
"github.com/devfile/library/v2/pkg/util"
Expand All @@ -36,7 +32,10 @@
// absolute path of devfile
absPath string

// relative path of devfile
// relative path of devfile.
// It can also be a relative or absolute path to a folder containing one or more devfiles,
// in which case the library will try to pick an existing one, based on the following priority order:
// devfile.yaml > .devfile.yaml > devfile.yml > .devfile.yml
relPath string

// raw content of the devfile
Expand Down Expand Up @@ -96,23 +95,16 @@

// Populate fills the DevfileCtx struct with relevant context info
func (d *DevfileCtx) Populate() (err error) {
rm3l marked this conversation as resolved.
Show resolved Hide resolved
if !strings.HasSuffix(d.relPath, ".yaml") {
if _, err := os.Stat(filepath.Join(d.relPath, "devfile.yaml")); os.IsNotExist(err) {
if _, err := os.Stat(filepath.Join(d.relPath, ".devfile.yaml")); os.IsNotExist(err) {
return fmt.Errorf("the provided path is not a valid yaml filepath, and devfile.yaml or .devfile.yaml not found in the provided path : %s", d.relPath)
} else {
d.relPath = filepath.Join(d.relPath, ".devfile.yaml")
}
} else {
d.relPath = filepath.Join(d.relPath, "devfile.yaml")
}
d.relPath, err = lookupDevfileFromPath(d.fs, d.relPath)
if err != nil {
return err

Check warning on line 100 in pkg/devfile/parser/context/context.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/context/context.go#L98-L100

Added lines #L98 - L100 were not covered by tests
}
if err := d.SetAbsPath(); err != nil {
if err = d.SetAbsPath(); err != nil {

Check warning on line 102 in pkg/devfile/parser/context/context.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/context/context.go#L102

Added line #L102 was not covered by tests
return err
}
klog.V(4).Infof("absolute devfile path: '%s'", d.absPath)
// Read and save devfile content
if err := d.SetDevfileContent(); err != nil {
if err = d.SetDevfileContent(); err != nil {

Check warning on line 107 in pkg/devfile/parser/context/context.go

View check run for this annotation

Codecov / codecov/patch

pkg/devfile/parser/context/context.go#L107

Added line #L107 was not covered by tests
return err
}
return d.populateDevfile()
Expand Down
Loading
Loading