internal: add new blueprintload package

This commit provides a `blueprintload` package that can be used
to load blueprints from json/toml from a path. This will be used
in `bootc-image-builder` and `image-builder-cli` and should
eventually be merged into `images`.
This commit is contained in:
Michael Vogt 2024-12-02 16:24:13 +01:00 committed by Simon de Vlieger
parent 830528fa15
commit f242005672
2 changed files with 139 additions and 0 deletions

View file

@ -0,0 +1,67 @@
package blueprintload
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"github.com/BurntSushi/toml"
"github.com/osbuild/images/pkg/blueprint"
)
// XXX: move this helper into images, share with bib
func decodeToml(r io.Reader, what string) (*blueprint.Blueprint, error) {
dec := toml.NewDecoder(r)
var conf blueprint.Blueprint
_, err := dec.Decode(&conf)
if err != nil {
return nil, fmt.Errorf("cannot decode %q: %w", what, err)
}
return &conf, nil
}
func decodeJson(r io.Reader, what string) (*blueprint.Blueprint, error) {
dec := json.NewDecoder(r)
dec.DisallowUnknownFields()
var conf blueprint.Blueprint
if err := dec.Decode(&conf); err != nil {
return nil, fmt.Errorf("cannot decode %q: %w", what, err)
}
if dec.More() {
return nil, fmt.Errorf("multiple configuration objects or extra data found in %q", what)
}
return &conf, nil
}
func Load(path string) (*blueprint.Blueprint, error) {
var fp io.ReadCloser
var err error
switch path {
case "":
return &blueprint.Blueprint{}, nil
case "-":
fp = os.Stdin
default:
fp, err = os.Open(path)
if err != nil {
return nil, err
}
defer fp.Close()
}
switch {
case path == "-", filepath.Ext(path) == ".json":
return decodeJson(fp, path)
case filepath.Ext(path) == ".toml":
return decodeToml(fp, path)
default:
return nil, fmt.Errorf("unsupported file extension for %q (please use .toml or .json)", path)
}
}

View file

@ -0,0 +1,72 @@
package blueprintload_test
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/osbuild/images/pkg/blueprint"
"github.com/osbuild/image-builder-cli/internal/blueprintload"
)
var testBlueprintJSON = `{
"customizations": {
"user": [
{
"name": "alice"
}
]
}
}`
var testBlueprintTOML = `
[[customizations.user]]
name = "alice"
`
var expectedBlueprint = &blueprint.Blueprint{
Customizations: &blueprint.Customizations{
User: []blueprint.UserCustomization{
{
Name: "alice",
},
},
},
}
func makeTestBlueprint(t *testing.T, name, content string) string {
tmpdir := t.TempDir()
blueprintPath := filepath.Join(tmpdir, name)
err := os.WriteFile(blueprintPath, []byte(content), 0644)
assert.NoError(t, err)
return blueprintPath
}
func TestBlueprintLoadJSON(t *testing.T) {
for _, tc := range []struct {
fname string
content string
expectedBp *blueprint.Blueprint
expectedError string
}{
{"bp.json", testBlueprintJSON, expectedBlueprint, ""},
{"bp.toml", testBlueprintTOML, expectedBlueprint, ""},
{"bp.toml", "wrong-content", nil, `cannot decode .*/bp.toml": toml: `},
{"bp.json", "wrong-content", nil, `cannot decode .*/bp.json": invalid `},
{"bp", "wrong-content", nil, `unsupported file extension for "/.*/bp"`},
} {
blueprintPath := makeTestBlueprint(t, tc.fname, tc.content)
bp, err := blueprintload.Load(blueprintPath)
if tc.expectedError == "" {
assert.NoError(t, err)
assert.Equal(t, tc.expectedBp, bp)
} else {
assert.NotNil(t, err)
assert.Regexp(t, tc.expectedError, err.Error())
}
}
}