add source for containers, packages
Some checks failed
Lint / golangci-lint (push) Failing after 9m44s
Declarative Tests / test (push) Failing after 14s
Declarative Tests / build-fedora (push) Failing after 2m50s
Declarative Tests / build-ubuntu-focal (push) Failing after 1m3s

This commit is contained in:
Matthew Rich 2024-07-01 00:16:55 -07:00
parent e604806f81
commit 52c083a3d9
17 changed files with 879 additions and 43 deletions

View File

@ -18,7 +18,15 @@ type Decoder interface {
Decode(v any) error Decode(v any) error
} }
func NewDecoder() *Decoder { func NewDecoder(r io.Reader, format Format) Decoder {
switch format {
case FormatYaml:
return NewYAMLDecoder(r)
case FormatJson:
return NewJSONDecoder(r)
case FormatProtoBuf:
return NewProtoBufDecoder(r)
}
return nil return nil
} }

View File

@ -18,7 +18,15 @@ type Encoder interface {
Close() error Close() error
} }
func NewEncoder() *Encoder { func NewEncoder(w io.Writer, format Format) Encoder {
switch format {
case FormatYaml:
return NewYAMLEncoder(w)
case FormatJson:
return NewJSONEncoder(w)
case FormatProtoBuf:
return NewProtoBufEncoder(w)
}
return nil return nil
} }

61
internal/codec/types.go Normal file
View File

@ -0,0 +1,61 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package codec
import (
"errors"
"encoding/json"
"gopkg.in/yaml.v3"
)
const (
FormatYaml Format = "yaml"
FormatJson Format = "json"
FormatProtoBuf Format = "protobuf"
)
var ErrInvalidFormat error = errors.New("invalid Format value")
type Format string
func (f *Format) Validate() error {
switch *f {
case FormatYaml, FormatJson, FormatProtoBuf:
return nil
default:
return ErrInvalidFormat
}
}
func (f *Format) Set(value string) (err error) {
if err = (*Format)(&value).Validate(); err == nil {
*f = Format(value)
}
return
}
func (f *Format) UnmarshalValue(value string) error {
switch value {
case string(FormatYaml), string(FormatJson), string(FormatProtoBuf):
*f = Format(value)
return nil
default:
return ErrInvalidFormat
}
}
func (f *Format) UnmarshalJSON(data []byte) error {
var s string
if unmarshalFormatTypeErr := json.Unmarshal(data, &s); unmarshalFormatTypeErr != nil {
return unmarshalFormatTypeErr
}
return f.UnmarshalValue(s)
}
func (f *Format) UnmarshalYAML(value *yaml.Node) error {
var s string
if err := value.Decode(&s); err != nil {
return err
}
return f.UnmarshalValue(s)
}

View File

@ -0,0 +1,41 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package codec
import (
_ "fmt"
"github.com/stretchr/testify/assert"
_ "log"
"testing"
)
type TestDec struct {
FormatType Format `yaml:"formattype" json:"formattype"`
}
func TestFormatType(t *testing.T) {
yamlData := `
formattype: json
`
v := &TestDec{}
dec := NewYAMLStringDecoder(yamlData)
e := dec.Decode(v)
assert.Nil(t, e)
assert.Equal(t, FormatJson, v.FormatType)
}
func TestFormatTypeErr(t *testing.T) {
yamlData := `
formattype: foo
`
v := &TestDec{}
dec := NewYAMLStringDecoder(yamlData)
e := dec.Decode(v)
assert.ErrorIs(t, ErrInvalidFormat, e)
}

143
internal/command/command.go Normal file
View File

@ -0,0 +1,143 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package command
import (
_ "context"
"encoding/json"
"fmt"
"gopkg.in/yaml.v3"
"io"
"log/slog"
_ "net/url"
"os"
"os/exec"
"strings"
"text/template"
"decl/internal/codec"
)
type CommandExecutor func(value any) ([]byte, error)
type CommandExtractAttributes func(output []byte, target any) error
type CommandArg string
type Command struct {
Path string `json:"path" yaml:"path"`
Args []CommandArg `json:"args" yaml:"args"`
Env []string `json:"env" yaml:"env"`
Split bool `json:"split" yaml:"split"`
FailOnError bool `json:"failonerror" yaml:"failonerror"`
Executor CommandExecutor `json:"-" yaml:"-"`
Extractor CommandExtractAttributes `json:"-" yaml:"-"`
}
func NewCommand() *Command {
c := &Command{ Split: true, FailOnError: true }
c.Executor = func(value any) ([]byte, error) {
args, err := c.Template(value)
if err != nil {
return nil, err
}
cmd := exec.Command(c.Path, args...)
c.SetCmdEnv(cmd)
slog.Info("execute() - cmd", "path", c.Path, "args", args)
output, stdoutPipeErr := cmd.StdoutPipe()
if stdoutPipeErr != nil {
return nil, stdoutPipeErr
}
stderr, pipeErr := cmd.StderrPipe()
if pipeErr != nil {
return nil, pipeErr
}
if startErr := cmd.Start(); startErr != nil {
return nil, startErr
}
slog.Info("execute() - start", "cmd", cmd)
stdOutOutput, _ := io.ReadAll(output)
stdErrOutput, _ := io.ReadAll(stderr)
slog.Info("execute() - io", "stdout", string(stdOutOutput), "stderr", string(stdErrOutput))
waitErr := cmd.Wait()
slog.Info("execute()", "path", c.Path, "args", args, "output", string(stdOutOutput), "error", string(stdErrOutput))
if len(stdErrOutput) > 0 && c.FailOnError {
return stdOutOutput, fmt.Errorf("%w %s", waitErr, string(stdErrOutput))
}
return stdOutOutput, waitErr
}
return c
}
func (c *Command) Load(r io.Reader) error {
return codec.NewYAMLDecoder(r).Decode(c)
}
func (c *Command) LoadDecl(yamlResourceDeclaration string) error {
return codec.NewYAMLStringDecoder(yamlResourceDeclaration).Decode(c)
}
func (c *Command) SetCmdEnv(cmd *exec.Cmd) {
cmd.Env = append(os.Environ(), c.Env...)
}
func (c *Command) Exists() bool {
if _, err := exec.LookPath(c.Path); err != nil {
return false
}
return true
}
func (c *Command) Template(value any) ([]string, error) {
var args []string = make([]string, 0, len(c.Args) * 2)
for i, arg := range c.Args {
var commandLineArg strings.Builder
err := template.Must(template.New(fmt.Sprintf("arg%d", i)).Parse(string(arg))).Execute(&commandLineArg, value)
if err != nil {
return nil, err
}
if commandLineArg.Len() > 0 {
var splitArg []string
if c.Split {
splitArg = strings.Split(commandLineArg.String(), " ")
} else {
splitArg = []string{commandLineArg.String()}
}
slog.Info("Template()", "split", splitArg, "len", len(splitArg))
args = append(args, splitArg...)
}
}
slog.Info("Template()", "Args", c.Args, "lencargs", len(c.Args), "args", args, "lenargs", len(args), "value", value)
return args, nil
}
func (c *Command) Execute(value any) ([]byte, error) {
return c.Executor(value)
}
func (c *CommandArg) UnmarshalValue(value string) error {
*c = CommandArg(value)
return nil
}
func (c *CommandArg) UnmarshalJSON(data []byte) error {
var s string
if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil {
return unmarshalRouteTypeErr
}
return c.UnmarshalValue(s)
}
func (c *CommandArg) UnmarshalYAML(value *yaml.Node) error {
var s string
if err := value.Decode(&s); err != nil {
return err
}
return c.UnmarshalValue(s)
}

View File

@ -0,0 +1,60 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package command
import (
_ "fmt"
"github.com/stretchr/testify/assert"
_ "os"
_ "strings"
"testing"
)
func TestNewCommand(t *testing.T) {
c := NewCommand()
assert.NotNil(t, c)
}
func TestCommandLoad(t *testing.T) {
c := NewCommand()
assert.NotNil(t, c)
decl := `
path: find
args:
- "{{ .Path }}"
`
assert.Nil(t, c.LoadDecl(decl))
assert.Equal(t, "find", c.Path)
}
func TestCommandTemplate(t *testing.T) {
c := NewCommand()
assert.NotNil(t, c)
decl := `
path: find
args:
- "{{ .Path }}"
`
assert.Nil(t, c.LoadDecl(decl))
assert.Equal(t, "find", c.Path)
assert.Equal(t, 1, len(c.Args))
f := struct { Path string } {
Path: "./",
}
args, templateErr := c.Template(f)
assert.Nil(t, templateErr)
assert.Equal(t, 1, len(args))
assert.Equal(t, "./", string(args[0]))
out, err := c.Execute(f)
assert.Nil(t, err)
assert.Greater(t, len(out), 0)
}

View File

@ -25,12 +25,13 @@ type Exec struct {
UpdateTemplate Command `yaml:"update" json:"update"` UpdateTemplate Command `yaml:"update" json:"update"`
DeleteTemplate Command `yaml:"delete" json:"delete"` DeleteTemplate Command `yaml:"delete" json:"delete"`
config ConfigurationValueGetter
// state attributes // state attributes
State string `yaml:"state"` State string `yaml:"state"`
} }
func init() { func init() {
ResourceTypes.Register("exec", func(u *url.URL) Resource { ResourceTypes.Register([]string{"exec"}, func(u *url.URL) Resource {
x := NewExec() x := NewExec()
return x return x
}) })
@ -75,6 +76,10 @@ func (x *Exec) SetURI(uri string) error {
return e return e
} }
func (x *Exec) UseConfig(config ConfigurationValueGetter) {
x.config = config
}
func (x *Exec) ResolveId(ctx context.Context) string { func (x *Exec) ResolveId(ctx context.Context) string {
return "" return ""
} }

View File

@ -0,0 +1,64 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package source
import (
"context"
_ "encoding/json"
_ "fmt"
_ "gopkg.in/yaml.v3"
"net/url"
_ "path/filepath"
"decl/internal/resource"
_ "os"
_ "io"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"log/slog"
)
type Container struct {
apiClient resource.ContainerClient
}
func NewContainer(containerClientApi resource.ContainerClient) *Container {
var apiClient resource.ContainerClient = containerClientApi
if apiClient == nil {
var err error
apiClient, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
}
}
return &Container{
apiClient: apiClient,
}
}
func init() {
SourceTypes.Register([]string{"container"}, func(u *url.URL) DocSource {
c := NewContainer(nil)
return c
})
}
func (c *Container) Type() string { return "container" }
func (c *Container) ExtractResources(filter ResourceSelector) ([]*resource.Document, error) {
ctx := context.Background()
slog.Info("container source ExtractResources()", "container", c)
containers, err := c.apiClient.ContainerList(ctx, types.ContainerListOptions{All: true})
if err != nil {
return nil, err
}
document := resource.NewDocument()
for _, container := range containers {
runningContainer := resource.NewContainer(nil)
runningContainer.Inspect(ctx, container.ID)
document.AddResourceDeclaration("container", runningContainer)
}
return []*resource.Document{document}, nil
}

View File

@ -0,0 +1,62 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package source
import (
_ "context"
_ "encoding/json"
_ "fmt"
_ "gopkg.in/yaml.v3"
"net/url"
_ "path/filepath"
"decl/internal/resource"
_ "os"
_ "io"
"log/slog"
)
type Package struct {
PackageType resource.PackageType `yaml:"type" json:"type"`
}
func NewPackage() *Package {
return &Package{ PackageType: resource.SystemPackageType }
}
func init() {
SourceTypes.Register([]string{"package"}, func(u *url.URL) DocSource {
p := NewPackage()
return p
})
}
func (p *Package) Type() string { return "package" }
func (p *Package) ExtractResources(filter ResourceSelector) ([]*resource.Document, error) {
documents := make([]*resource.Document, 0, 100)
slog.Info("package source ExtractResources()", "package", p)
installedPackages := make([]*resource.Package, 0, 100)
cmd := p.PackageType.NewReadPackagesCommand()
if out, err := cmd.Execute(p); err == nil {
slog.Info("package source ExtractResources()", "output", out)
if exErr := cmd.Extractor(out, &installedPackages); exErr != nil {
return documents, exErr
}
document := resource.NewDocument()
for _, pkg := range installedPackages {
if pkg == nil {
pkg = resource.NewPackage()
}
pkg.PackageType = p.PackageType
document.AddResourceDeclaration("package", pkg)
}
documents = append(documents, document)
} else {
slog.Info("package source ExtractResources()", "output", out, "error", err)
return documents, err
}
return documents, nil
}

View File

@ -0,0 +1,23 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package source
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestNewPackageSource(t *testing.T) {
s := NewPackage()
assert.NotNil(t, s)
}
func TestExtractPackages(t *testing.T) {
p := NewPackage()
assert.NotNil(t, p)
document, err := p.ExtractResources(nil)
assert.Nil(t, err)
assert.NotNil(t, document)
assert.Greater(t, len(document), 0)
}

116
internal/transport/file.go Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package transport
import (
_ "errors"
"path/filepath"
"io"
"os"
"net/url"
"strings"
"fmt"
"compress/gzip"
)
type File struct {
uri *url.URL
path string
exttype string
fileext string
readHandle *os.File
writeHandle *os.File
gzip bool
gzipWriter io.WriteCloser
gzipReader io.ReadCloser
}
func NewFile(u *url.URL) (f *File, err error) {
f = &File {
uri: u,
path: filepath.Join(u.Hostname(), u.RequestURI()),
}
f.extension()
f.DetectGzip()
if f.path == "" || f.path == "-" {
f.readHandle = os.Stdin
f.writeHandle = os.Stdout
} else {
if f.readHandle, err = os.Open(f.Path()); err != nil {
return
}
f.writeHandle = f.readHandle
}
if f.Gzip() {
f.gzipWriter = gzip.NewWriter(f.writeHandle)
if f.gzipReader, err = gzip.NewReader(f.readHandle); err != nil {
return
}
}
return
}
func (f *File) extension() {
elements := strings.Split(f.path, ".")
numberOfElements := len(elements)
if numberOfElements > 2 {
f.exttype = elements[numberOfElements - 2]
f.fileext = elements[numberOfElements - 1]
}
f.exttype = elements[numberOfElements - 1]
}
func (f *File) DetectGzip() {
f.gzip = (f.uri.Query().Get("gzip") == "true" || f.fileext == "gz")
}
func (f *File) URI() *url.URL {
return f.uri
}
func (f *File) Path() string {
return f.path
}
func (f *File) Signature() (documentSignature string) {
if signatureResp, signatureErr := os.Open(fmt.Sprintf("%s.sig", f.uri.String())); signatureErr == nil {
defer signatureResp.Close()
readSignatureBody, readSignatureErr := io.ReadAll(signatureResp)
if readSignatureErr == nil {
documentSignature = string(readSignatureBody)
} else {
panic(readSignatureErr)
}
} else {
panic(signatureErr)
}
return documentSignature
}
func (f *File) ContentType() string {
return f.exttype
}
func (f *File) SetGzip(gzip bool) {
f.gzip = gzip
}
func (f *File) Gzip() bool {
return f.gzip
}
func (f *File) Reader() io.ReadCloser {
if f.Gzip() {
return f.gzipReader
}
return f.readHandle
}
func (f *File) Writer() io.WriteCloser {
if f.Gzip() {
return f.gzipWriter
}
return f.writeHandle
}

View File

@ -0,0 +1,39 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package transport
import (
"github.com/stretchr/testify/assert"
"testing"
"fmt"
"os"
"net/url"
"path/filepath"
)
var TransportFileTestFile = fmt.Sprintf("%s/foo", TempDir)
func TestNewTransportFileReader(t *testing.T) {
path := fmt.Sprintf("%s/foo", TempDir)
u, e := url.Parse(fmt.Sprintf("file://%s", path))
assert.Nil(t, e)
writeErr := os.WriteFile(path, []byte("test"), 0644)
assert.Nil(t, writeErr)
file, err := NewFile(u)
assert.Nil(t, err)
assert.Equal(t, file.Path(), path)
}
func TestNewTransportFileReaderExtension(t *testing.T) {
u, e := url.Parse(fmt.Sprintf("file://%s.yaml", TransportFileTestFile))
assert.Nil(t, e)
f := &File{
uri: u,
path: filepath.Join(u.Hostname(), u.RequestURI()),
}
f.extension()
assert.Equal(t, f.exttype, "yaml")
}

119
internal/transport/http.go Normal file
View File

@ -0,0 +1,119 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package transport
import (
_ "errors"
"io"
_ "os"
"net/url"
"net/http"
"strings"
"fmt"
"bytes"
"context"
"path/filepath"
)
type BufferCloser struct {
stream io.Closer
*bytes.Buffer
}
type HTTP struct {
uri *url.URL
path string
exttype string
fileext string
buffer BufferCloser
getRequest *http.Request
getResponse *http.Response
postRequest *http.Request
postResponse *http.Response
Client *http.Client
}
func (b BufferCloser) Close() error {
if b.stream != nil {
return b.stream.Close()
}
return nil
}
func NewHTTP(u *url.URL, ctx context.Context) (h *HTTP, err error) {
h = &HTTP {
uri: u,
path: filepath.Join(u.Hostname(), u.RequestURI()),
Client: http.DefaultClient,
}
h.extension()
h.postRequest, err = http.NewRequestWithContext(ctx, "POST", u.String(), h.buffer)
h.getRequest, err = http.NewRequestWithContext(ctx, "GET", u.String(), nil)
return
}
func (h *HTTP) extension() {
elements := strings.Split(h.path, ".")
numberOfElements := len(elements)
if numberOfElements > 2 {
h.exttype = elements[numberOfElements - 2]
h.fileext = elements[numberOfElements - 1]
}
h.exttype = elements[numberOfElements - 1]
}
func (h *HTTP) URI() *url.URL {
return h.uri
}
func (h *HTTP) Path() string {
return h.path
}
func (h *HTTP) Signature() (documentSignature string) {
if h.getResponse != nil {
documentSignature := h.getResponse.Header.Get("Signature")
if documentSignature == "" {
signatureResp, signatureErr := h.Client.Get(fmt.Sprintf("%s.sig", h.uri.String()))
if signatureErr == nil {
defer signatureResp.Body.Close()
readSignatureBody, readSignatureErr := io.ReadAll(signatureResp.Body)
if readSignatureErr == nil {
documentSignature = string(readSignatureBody)
}
}
}
}
return documentSignature
}
func (h *HTTP) ContentType() (contenttype string) {
contenttype = h.getResponse.Header.Get("Content-Type")
switch contenttype {
case "application/octet-stream":
return h.exttype
default:
}
return
}
func (h *HTTP) Gzip() bool {
return h.fileext == "gz"
}
func (h *HTTP) Reader() io.ReadCloser {
var err error
if h.getResponse, err = h.Client.Do(h.getRequest); err != nil {
panic(err)
}
return h.getResponse.Body
}
func (h *HTTP) Writer() io.WriteCloser {
var err error
if h.postResponse, err = h.Client.Do(h.postRequest); err != nil {
h.postResponse, err = h.Client.Do(h.postRequest)
}
return h.buffer
}

View File

@ -0,0 +1,21 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package transport
import (
"github.com/stretchr/testify/assert"
"testing"
_ "fmt"
_ "os"
"net/url"
_ "path/filepath"
"context"
)
func TestNewTransportHTTPReader(t *testing.T) {
u, urlErr := url.Parse("https://localhost/resource")
assert.Nil(t, urlErr)
h, err := NewHTTP(u, context.Background())
assert.Nil(t, err)
assert.NotNil(t, h)
}

View File

@ -4,48 +4,65 @@ package transport
import ( import (
_ "errors" _ "errors"
"fmt" _ "fmt"
"net/url" "net/url"
"net/http" _ "net/http"
_ "strings" _ "strings"
"path/filepath" _ "path/filepath"
"io" "io"
"os" _ "os"
"context"
) )
type Handler interface {
URI() *url.URL
ContentType() string
Gzip() bool
Signature() string
Reader() io.ReadCloser
Writer() io.WriteCloser
}
type Reader struct { type Reader struct {
uri *url.URL uri *url.URL
handle any handle Handler
stream io.ReadCloser stream io.ReadCloser
} }
func NewReader(u *url.URL) (*Reader, error) { func NewReader(u *url.URL) (reader *Reader, e error) {
var e error ctx := context.Background()
r := &Reader{ uri: u } reader = &Reader{ uri: u }
switch u.Scheme { switch u.Scheme {
case "http", "https": case "http", "https":
resp, err := http.Get(u.String()) reader.handle, e = NewHTTP(u, ctx)
r.handle = resp
r.stream = resp.Body
e = err
case "file": case "file":
fallthrough fallthrough
default: default:
path := filepath.Join(u.Hostname(), u.RequestURI()) reader.handle, e = NewFile(u)
file, err := os.Open(path)
r.handle = file
r.stream = file
e = err
} }
return r, e reader.stream = reader.handle.Reader()
return
} }
type Writer struct { type Writer struct {
uri *url.URL
handle Handler
stream io.WriteCloser
} }
func NewWriter(url *url.URL) (*Writer, error) { func NewWriter(u *url.URL) (writer *Writer, e error) {
return nil, nil ctx := context.Background()
writer = &Writer{ uri: u }
switch u.Scheme {
case "http", "https":
writer.handle, e = NewHTTP(u, ctx)
case "file":
fallthrough
default:
writer.handle, e = NewFile(u)
}
writer.stream = writer.handle.Writer()
return writer, e
} }
func (r *Reader) Read(b []byte) (int, error) { func (r *Reader) Read(b []byte) (int, error) {
@ -56,17 +73,36 @@ func (r *Reader) Close() error {
return r.stream.Close() return r.stream.Close()
} }
func (r *Reader) ContentType() string {
return r.handle.ContentType()
}
func (r *Reader) Gzip() bool {
return r.handle.Gzip()
}
func (r *Reader) Signature() string { func (r *Reader) Signature() string {
documentSignature := r.handle.(*http.Response).Header.Get("Signature") return r.handle.Signature()
if documentSignature == "" {
signatureResp, signatureErr := http.Get(fmt.Sprintf("%s.sig", r.uri.String()))
if signatureErr == nil {
defer signatureResp.Body.Close()
readSignatureBody, readSignatureErr := io.ReadAll(signatureResp.Body)
if readSignatureErr == nil {
documentSignature = string(readSignatureBody)
} }
func (w *Writer) Write(b []byte) (int, error) {
return w.stream.Write(b)
} }
func (w *Writer) Close() error {
return w.stream.Close()
} }
return documentSignature
func (w *Writer) ContentType() string {
return w.handle.ContentType()
}
func (w *Writer) Gzip() bool {
return w.handle.Gzip()
}
func (w *Writer) Signature() string {
return w.handle.Signature()
} }

View File

@ -13,6 +13,14 @@ import (
var TempDir string var TempDir string
var testFileResourceDoc string = `
resources:
- type: file
transition: read
attributes:
path: /tmp/foobar
`
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
var err error var err error
TempDir, err = os.MkdirTemp("", "testtransportfile") TempDir, err = os.MkdirTemp("", "testtransportfile")
@ -38,3 +46,19 @@ func TestNewTransportReader(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, reader) assert.NotNil(t, reader)
} }
func TestTransportReaderContentType(t *testing.T) {
path := fmt.Sprintf("%s/foo.jx.yaml", TempDir)
u, e := url.Parse(fmt.Sprintf("file://%s", path))
assert.Nil(t, e)
writeErr := os.WriteFile(path, []byte(testFileResourceDoc), 0644)
assert.Nil(t, writeErr)
reader, err := NewReader(u)
assert.Nil(t, err)
assert.NotNil(t, reader)
assert.Equal(t, reader.ContentType(), "yaml")
}

View File

@ -10,11 +10,17 @@ import (
"path/filepath" "path/filepath"
) )
/*
The `types` package provides a generic method of registering a type factory.
*/
var ( var (
ErrUnknownType = errors.New("Unknown type") ErrUnknownType = errors.New("Unknown type")
) )
//type Name string //`json:"type"` //type Name[Registry any] string //`json:"type"`
type Factory[Product any] func(*url.URL) Product type Factory[Product any] func(*url.URL) Product
type RegistryTypeMap[Product any] map[string]Factory[Product] type RegistryTypeMap[Product any] map[string]Factory[Product]
@ -85,12 +91,12 @@ func (t *Types[Product]) Get(typename string) Factory[Product] {
} }
/* /*
func (n *Name) UnmarshalJSON(b []byte) error { func (n *Name[Registry]) UnmarshalJSON(b []byte) error {
TypeName := strings.Trim(string(b), "\"") ProductTypeName := strings.Trim(string(b), "\"")
if SourceTypes.Has(TypeName) { if Registry.Has(ProductTypeName) {
*n = TypeName(TypeName) *n = Name[Registry](ProductTypeName)
return nil return nil
} }
return fmt.Errorf("%w: %s", ErrUnknownType, TypeName) return fmt.Errorf("%w: %s", ErrUnknownType, ProductTypeName)
} }
*/ */