diff --git a/README.md b/README.md index 97fa189..b04929e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Reference library for the parsing and loading SCORE files in Go. This can be added to your project via: ```sh -$ go get -u github.com/score-spec/score-go@latest +go get -u github.com/score-spec/score-go@latest ``` **NOTE**: if you project is still using the hand-written types, you will need to stay on `github.com/score-spec/score-go@v0.0.1` @@ -17,58 +17,61 @@ This library includes a few utility methods to parse source SCORE files. ```go import ( - "io" "os" - + "github.com/score-spec/score-go/loader" + "github.com/score-spec/score-go/schema" score "github.com/score-spec/score-go/types" ) func main() { - var ( - err error - src io.Reader - ) + src, err := os.Open("score.yaml") + if err != nil { + panic(err) + } + defer src.Close() - if src, err = os.Open("score.yaml"); err != nil { + var srcMap map[string]interface{} + if err := loader.ParseYAML(&srcMap, src); err != nil { panic(err) } - defer src.Close() - var srcMap map[string]interface{} - if err = loader.ParseYAML(&srcMap, src); err != nil { + if err := schema.Validate(srcMap); err != nil { + panic(err) + } + + var spec score.Workload + if err := loader.MapSpec(&spec, srcMap); err != nil { panic(err) } - var spec score.WorkloadSpec - if err = loader.MapSpec(&spec, srcMap); err != nil { + if err := loader.Normalize(&spec, "."); err != nil { panic(err) } // Do something with the spec // ... } - ``` ## Upgrading the schema version -When the Score JSON schema is updated in https://github.com/score-spec/schema, this repo should be updated to match. +When the Score JSON schema is updated in , this repo should be updated to match. First update the subtree: -``` +```sh make update-schema ``` Then regenerate the defined types: -``` +```sh make generate ``` And ensure the tests still pass: -``` +```sh go test -v ./... ``` diff --git a/loader/fixtures/test_file.txt b/loader/fixtures/test_file.txt new file mode 100644 index 0000000..557db03 --- /dev/null +++ b/loader/fixtures/test_file.txt @@ -0,0 +1 @@ +Hello World diff --git a/loader/normalize.go b/loader/normalize.go new file mode 100644 index 0000000..d38fd5d --- /dev/null +++ b/loader/normalize.go @@ -0,0 +1,43 @@ +package loader + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/score-spec/score-go/types" +) + +// Normalize normalizes the target Workload by: +// * embedding container file sources as content +func Normalize(w *types.Workload, baseDir string) error { + for name, c := range w.Containers { + for i, f := range c.Files { + if f.Source != nil { + raw, err := readFile(baseDir, *f.Source) + if err != nil { + return fmt.Errorf("embedding file '%s' for container '%s': %w", *f.Source, name, err) + } + + c.Files[i].Source = nil + c.Files[i].Content = &raw + } + } + } + + return nil +} + +// readFile reads a text file into memory +func readFile(baseDir, path string) (string, error) { + if !filepath.IsAbs(path) { + path = filepath.Join(baseDir, path) + } + + raw, err := os.ReadFile(path) + if err != nil { + return "", err + } + + return string(raw), nil +} diff --git a/loader/normalize_test.go b/loader/normalize_test.go new file mode 100644 index 0000000..62fa2ed --- /dev/null +++ b/loader/normalize_test.go @@ -0,0 +1,94 @@ +package loader + +import ( + "errors" + "io" + "testing" + + "github.com/score-spec/score-go/types" + "github.com/stretchr/testify/assert" +) + +func TestNormalize(t *testing.T) { + var tests = []struct { + Name string + Source io.Reader + Input *types.Workload + Output *types.Workload + Error error + }{ + { + Name: "Embeds source file", + Input: &types.Workload{ + ApiVersion: "score.dev/v1b1", + Metadata: types.WorkloadMetadata{ + "name": "hello-world", + }, + Containers: types.WorkloadContainers{ + "hello": types.Container{ + Files: []types.ContainerFilesElem{ + { + Source: stringRef("./test_file.txt"), + Target: "/etc/hello-world/config.yaml", + Mode: stringRef("666"), + NoExpand: boolRef(true), + }, + }, + }, + }, + }, + Output: &types.Workload{ + ApiVersion: "score.dev/v1b1", + Metadata: types.WorkloadMetadata{ + "name": "hello-world", + }, + Containers: types.WorkloadContainers{ + "hello": types.Container{ + Files: []types.ContainerFilesElem{ + { + Target: "/etc/hello-world/config.yaml", + Mode: stringRef("666"), + Content: stringRef("Hello World\n"), + NoExpand: boolRef(true), + }, + }, + }, + }, + }, + }, + { + Name: "Errors when the source file does not exist", + Input: &types.Workload{ + ApiVersion: "score.dev/v1b1", + Metadata: types.WorkloadMetadata{ + "name": "hello-world", + }, + Containers: types.WorkloadContainers{ + "hello": types.Container{ + Files: []types.ContainerFilesElem{ + { + Source: stringRef("./not_existing.txt"), + Target: "/etc/hello-world/config.yaml", + Mode: stringRef("666"), + NoExpand: boolRef(true), + }, + }, + }, + }, + }, + Error: errors.New("embedding file './not_existing.txt' for container 'hello': open fixtures/not_existing.txt: no such file or directory"), + }, + } + + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + var err = Normalize(tt.Input, "./fixtures") + if tt.Error != nil { + assert.EqualError(t, err, tt.Error.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.Output, tt.Input) + } + }) + } +}