add source for containers, packages
This commit is contained in:
parent
e604806f81
commit
52c083a3d9
@ -18,7 +18,15 @@ type Decoder interface {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,15 @@ type Encoder interface {
|
||||
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
|
||||
}
|
||||
|
||||
|
61
internal/codec/types.go
Normal file
61
internal/codec/types.go
Normal 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)
|
||||
}
|
41
internal/codec/types_test.go
Normal file
41
internal/codec/types_test.go
Normal 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
143
internal/command/command.go
Normal 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)
|
||||
}
|
60
internal/command/command_test.go
Normal file
60
internal/command/command_test.go
Normal 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)
|
||||
}
|
@ -25,12 +25,13 @@ type Exec struct {
|
||||
UpdateTemplate Command `yaml:"update" json:"update"`
|
||||
DeleteTemplate Command `yaml:"delete" json:"delete"`
|
||||
|
||||
config ConfigurationValueGetter
|
||||
// state attributes
|
||||
State string `yaml:"state"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
ResourceTypes.Register("exec", func(u *url.URL) Resource {
|
||||
ResourceTypes.Register([]string{"exec"}, func(u *url.URL) Resource {
|
||||
x := NewExec()
|
||||
return x
|
||||
})
|
||||
@ -75,6 +76,10 @@ func (x *Exec) SetURI(uri string) error {
|
||||
return e
|
||||
}
|
||||
|
||||
func (x *Exec) UseConfig(config ConfigurationValueGetter) {
|
||||
x.config = config
|
||||
}
|
||||
|
||||
func (x *Exec) ResolveId(ctx context.Context) string {
|
||||
return ""
|
||||
}
|
||||
|
64
internal/source/container.go
Normal file
64
internal/source/container.go
Normal 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
|
||||
}
|
62
internal/source/package.go
Normal file
62
internal/source/package.go
Normal 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
|
||||
}
|
23
internal/source/package_test.go
Normal file
23
internal/source/package_test.go
Normal 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
116
internal/transport/file.go
Normal 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
|
||||
}
|
39
internal/transport/file_test.go
Normal file
39
internal/transport/file_test.go
Normal 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
119
internal/transport/http.go
Normal 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
|
||||
}
|
21
internal/transport/http_test.go
Normal file
21
internal/transport/http_test.go
Normal 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)
|
||||
}
|
@ -4,48 +4,65 @@ package transport
|
||||
|
||||
import (
|
||||
_ "errors"
|
||||
"fmt"
|
||||
_ "fmt"
|
||||
"net/url"
|
||||
"net/http"
|
||||
_ "net/http"
|
||||
_ "strings"
|
||||
"path/filepath"
|
||||
_ "path/filepath"
|
||||
"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 {
|
||||
uri *url.URL
|
||||
handle any
|
||||
handle Handler
|
||||
stream io.ReadCloser
|
||||
}
|
||||
|
||||
func NewReader(u *url.URL) (*Reader, error) {
|
||||
var e error
|
||||
r := &Reader{ uri: u }
|
||||
func NewReader(u *url.URL) (reader *Reader, e error) {
|
||||
ctx := context.Background()
|
||||
reader = &Reader{ uri: u }
|
||||
switch u.Scheme {
|
||||
case "http", "https":
|
||||
resp, err := http.Get(u.String())
|
||||
r.handle = resp
|
||||
r.stream = resp.Body
|
||||
e = err
|
||||
reader.handle, e = NewHTTP(u, ctx)
|
||||
case "file":
|
||||
fallthrough
|
||||
default:
|
||||
path := filepath.Join(u.Hostname(), u.RequestURI())
|
||||
file, err := os.Open(path)
|
||||
r.handle = file
|
||||
r.stream = file
|
||||
e = err
|
||||
reader.handle, e = NewFile(u)
|
||||
}
|
||||
return r, e
|
||||
reader.stream = reader.handle.Reader()
|
||||
return
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
|
||||
uri *url.URL
|
||||
handle Handler
|
||||
stream io.WriteCloser
|
||||
}
|
||||
|
||||
func NewWriter(url *url.URL) (*Writer, error) {
|
||||
return nil, nil
|
||||
func NewWriter(u *url.URL) (writer *Writer, e error) {
|
||||
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) {
|
||||
@ -56,17 +73,36 @@ func (r *Reader) Close() error {
|
||||
return r.stream.Close()
|
||||
}
|
||||
|
||||
func (r *Reader) Signature() string {
|
||||
documentSignature := r.handle.(*http.Response).Header.Get("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)
|
||||
}
|
||||
}
|
||||
}
|
||||
return documentSignature
|
||||
func (r *Reader) ContentType() string {
|
||||
return r.handle.ContentType()
|
||||
}
|
||||
|
||||
func (r *Reader) Gzip() bool {
|
||||
return r.handle.Gzip()
|
||||
}
|
||||
|
||||
func (r *Reader) Signature() string {
|
||||
return r.handle.Signature()
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (w *Writer) Write(b []byte) (int, error) {
|
||||
return w.stream.Write(b)
|
||||
}
|
||||
|
||||
func (w *Writer) Close() error {
|
||||
return w.stream.Close()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
@ -13,6 +13,14 @@ import (
|
||||
|
||||
var TempDir string
|
||||
|
||||
var testFileResourceDoc string = `
|
||||
resources:
|
||||
- type: file
|
||||
transition: read
|
||||
attributes:
|
||||
path: /tmp/foobar
|
||||
`
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var err error
|
||||
TempDir, err = os.MkdirTemp("", "testtransportfile")
|
||||
@ -38,3 +46,19 @@ func TestNewTransportReader(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
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")
|
||||
}
|
||||
|
@ -10,11 +10,17 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
The `types` package provides a generic method of registering a type factory.
|
||||
|
||||
*/
|
||||
|
||||
var (
|
||||
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 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 {
|
||||
TypeName := strings.Trim(string(b), "\"")
|
||||
if SourceTypes.Has(TypeName) {
|
||||
*n = TypeName(TypeName)
|
||||
func (n *Name[Registry]) UnmarshalJSON(b []byte) error {
|
||||
ProductTypeName := strings.Trim(string(b), "\"")
|
||||
if Registry.Has(ProductTypeName) {
|
||||
*n = Name[Registry](ProductTypeName)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%w: %s", ErrUnknownType, TypeName)
|
||||
return fmt.Errorf("%w: %s", ErrUnknownType, ProductTypeName)
|
||||
}
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user