Fix an issue with the package resource where a missing package would cause a fatal error
WIP: add support container image build using local filesytem contexts or contextes generated from resource definitions WIP: added support for the create command in the exec resource Fix a type matching error in `types` package use of generics
This commit is contained in:
parent
a6426da6e1
commit
bcf4e768ff
4
Makefile
4
Makefile
@ -1,6 +1,6 @@
|
||||
IMAGE?=fedora:latest
|
||||
LDFLAGS?=--ldflags '-extldflags "-static"' --ldflags="-X 'main.commit=$(shell git rev-parse HEAD)' -X 'main.version=$(shell git describe --tags)' -X 'main.date=$(shell date '+%Y-%m-%d %T.%s%z')'"
|
||||
export CGO_ENABLED=1
|
||||
export CGO_ENABLED=0
|
||||
VERSION?=$(shell git describe --tags | sed -e 's/^v//' -e 's/-/_/g')
|
||||
.PHONY=jx-cli
|
||||
|
||||
@ -26,7 +26,7 @@ ubuntu-deps:
|
||||
deb: ubuntu-deps
|
||||
:
|
||||
run:
|
||||
docker run -it -v $(HOME)/.git-credentials:/root/.git-credentials -v $(HOME)/.gitconfig:/root/.gitconfig -v $(shell pwd):/src $(IMAGE) bash
|
||||
docker run -it -v $(HOME)/.git-credentials:/root/.git-credentials -v $(HOME)/.gitconfig:/root/.gitconfig -v $(shell pwd):/src $(IMAGE) sh
|
||||
clean:
|
||||
go clean -modcache
|
||||
rm jx
|
||||
|
6
examples/golang.jx.yaml
Normal file
6
examples/golang.jx.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
resources:
|
||||
- type: file
|
||||
transition: create
|
||||
attributes:
|
||||
path: go1.22.5.linux-amd64.tar.gz
|
||||
sourceref: https://go.dev/dl/go1.22.5.linux-amd64.tar.gz
|
2
go.mod
2
go.mod
@ -3,7 +3,7 @@ module decl
|
||||
go 1.22.5
|
||||
|
||||
require (
|
||||
gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240513200425-f413d8adf7b3
|
||||
// gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240513200425-f413d8adf7b3
|
||||
gitea.rosskeen.house/rosskeen.house/machine v0.0.0-20240520193117-1835255b6d02
|
||||
github.com/docker/docker v27.0.3+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
|
@ -39,6 +39,13 @@ type Command struct {
|
||||
|
||||
func NewCommand() *Command {
|
||||
c := &Command{ Split: true, FailOnError: true }
|
||||
c.Defaults()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Command) Defaults() {
|
||||
c.Split = true
|
||||
c.FailOnError = true
|
||||
c.CommandExists = func() error {
|
||||
if _, err := exec.LookPath(c.Path); err != nil {
|
||||
return fmt.Errorf("%w - %w", ErrUnknownCommand, err)
|
||||
@ -82,7 +89,6 @@ func NewCommand() *Command {
|
||||
}
|
||||
return stdOutOutput, waitErr
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Command) Load(r io.Reader) error {
|
||||
|
@ -33,7 +33,7 @@ func NewConfigFile() *ConfigFile {
|
||||
func NewConfigFileFromURI(u *url.URL) *ConfigFile {
|
||||
t := NewConfigFile()
|
||||
if u.Scheme == "file" {
|
||||
t.Path,_ = filepath.Abs(filepath.Join(u.Hostname(), u.RequestURI()))
|
||||
t.Path,_ = filepath.Abs(filepath.Join(u.Hostname(), u.Path))
|
||||
} else {
|
||||
t.Path = filepath.Join(u.Hostname(), u.Path)
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ func init() {
|
||||
ConfigSourceTypes.Register([]string{"fs"}, func(u *url.URL) ConfigSource {
|
||||
|
||||
t := NewConfigFS(nil)
|
||||
t.Path,_ = filepath.Abs(filepath.Join(u.Hostname(), u.RequestURI()))
|
||||
t.Path,_ = filepath.Abs(filepath.Join(u.Hostname(), u.Path))
|
||||
t.fsys = os.DirFS(t.Path)
|
||||
return t
|
||||
})
|
||||
|
@ -26,6 +26,7 @@ type ContainerImageClient interface {
|
||||
ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error)
|
||||
ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error)
|
||||
ImageRemove(ctx context.Context, imageID string, options image.RemoveOptions) ([]image.DeleteResponse, error)
|
||||
ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
@ -40,7 +41,10 @@ type ContainerImage struct {
|
||||
Size int64 `json:"size" yaml:"size"`
|
||||
Author string `json:"author,omitempty" yaml:"author,omitempty"`
|
||||
Comment string `json:"comment,omitempty" yaml:"comment,omitempty"`
|
||||
State string `yaml:"state,omitempty" json:"state,omitempty"`
|
||||
Dockerfile string `json:"dockerfile,omitempty" yaml:"dockerfile,omitempty"`
|
||||
ContextRef ResourceReference `json:"contextref,omitempty" yaml:"contextref,omitempty"`
|
||||
InjectJX bool `json:"injectjx,omitempty" yaml:"injectjx,omitempty"`
|
||||
State string `yaml:"state,omitempty" json:"state,omitempty"`
|
||||
|
||||
config ConfigurationValueGetter
|
||||
apiClient ContainerImageClient
|
||||
@ -67,6 +71,7 @@ func NewContainerImage(containerClientApi ContainerImageClient) *ContainerImage
|
||||
}
|
||||
return &ContainerImage{
|
||||
apiClient: apiClient,
|
||||
InjectJX: true,
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,6 +90,7 @@ func (c *ContainerImage) Clone() Resource {
|
||||
Size: c.Size,
|
||||
Author: c.Author,
|
||||
Comment: c.Comment,
|
||||
InjectJX: c.InjectJX,
|
||||
State: c.State,
|
||||
apiClient: c.apiClient,
|
||||
}
|
||||
@ -196,6 +202,17 @@ func (c *ContainerImage) Notify(m *machine.EventMessage) {
|
||||
c.State = "absent"
|
||||
panic(createErr)
|
||||
}
|
||||
case "start_update":
|
||||
if createErr := c.Update(ctx); createErr == nil {
|
||||
if triggerErr := c.stater.Trigger("updated"); triggerErr == nil {
|
||||
return
|
||||
} else {
|
||||
c.State = "absent"
|
||||
}
|
||||
} else {
|
||||
c.State = "absent"
|
||||
panic(createErr)
|
||||
}
|
||||
case "start_delete":
|
||||
if deleteErr := c.Delete(ctx); deleteErr == nil {
|
||||
if triggerErr := c.StateMachine().Trigger("deleted"); triggerErr == nil {
|
||||
@ -237,9 +254,37 @@ func (c *ContainerImage) LoadDecl(yamlResourceDeclaration string) error {
|
||||
}
|
||||
|
||||
func (c *ContainerImage) Create(ctx context.Context) error {
|
||||
buildOptions := types.ImageBuildOptions{
|
||||
Dockerfile: c.Dockerfile,
|
||||
Tags: []string{c.Name},
|
||||
}
|
||||
|
||||
if c.ContextRef.Exists() {
|
||||
if c.ContextRef.ContentType() == "tar" {
|
||||
ref := c.ContextRef.Lookup(c.Resources)
|
||||
reader, readerErr := ref.ContentReaderStream()
|
||||
if readerErr != nil {
|
||||
return readerErr
|
||||
}
|
||||
|
||||
buildResponse, buildErr := c.apiClient.ImageBuild(ctx, reader, buildOptions)
|
||||
if buildErr != nil {
|
||||
return buildErr
|
||||
}
|
||||
defer buildResponse.Body.Close()
|
||||
if _, outputErr := io.ReadAll(buildResponse.Body); outputErr != nil {
|
||||
return fmt.Errorf("%w %s %s", outputErr, c.Type(), c.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ContainerImage) Update(ctx context.Context) error {
|
||||
return c.Create(ctx)
|
||||
}
|
||||
|
||||
func (c *ContainerImage) Pull(ctx context.Context) error {
|
||||
out, err := c.apiClient.ImagePull(ctx, c.Name, image.PullOptions{})
|
||||
slog.Info("ContainerImage.Pull()", "name", c.Name, "error", err)
|
||||
@ -345,3 +390,5 @@ func (c *ContainerImage) ResolveId(ctx context.Context) string {
|
||||
}
|
||||
return c.Id
|
||||
}
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"context"
|
||||
"decl/tests/mocks"
|
||||
_ "encoding/json"
|
||||
_ "fmt"
|
||||
"fmt"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -96,33 +96,24 @@ func TestReadContainerImage(t *testing.T) {
|
||||
assert.Greater(t, len(resourceYaml), 0)
|
||||
}
|
||||
|
||||
/*
|
||||
func TestCreateContainerImage(t *testing.T) {
|
||||
m := &mocks.MockContainerClient{
|
||||
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
|
||||
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
||||
},
|
||||
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
|
||||
return nil
|
||||
InjectImageBuild: func(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
|
||||
return types.ImageBuildResponse{Body: io.NopCloser(strings.NewReader("image built")) }, nil
|
||||
},
|
||||
}
|
||||
|
||||
decl := `
|
||||
name: "testcontainer"
|
||||
decl := fmt.Sprintf(`
|
||||
name: "testcontainerimage"
|
||||
image: "alpine"
|
||||
state: present
|
||||
`
|
||||
contextref: file://%s
|
||||
`, "")
|
||||
c := NewContainerImage(m)
|
||||
stater := c.StateMachine()
|
||||
|
||||
e := c.LoadDecl(decl)
|
||||
assert.Equal(t, nil, e)
|
||||
assert.Equal(t, "testcontainer", c.Name)
|
||||
assert.Equal(t, "testcontainerimage", c.Name)
|
||||
|
||||
applyErr := c.Apply()
|
||||
assert.Equal(t, nil, applyErr)
|
||||
|
||||
c.State = "absent"
|
||||
|
||||
applyDeleteErr := c.Apply()
|
||||
assert.Equal(t, nil, applyDeleteErr)
|
||||
assert.Nil(t, stater.Trigger("create"))
|
||||
}
|
||||
*/
|
||||
|
@ -11,7 +11,7 @@ _ "errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
"log/slog"
|
||||
_ "gitea.rosskeen.house/rosskeen.house/machine"
|
||||
"gitea.rosskeen.house/pylon/luaruntime"
|
||||
//_ "gitea.rosskeen.house/pylon/luaruntime"
|
||||
"decl/internal/codec"
|
||||
"decl/internal/config"
|
||||
)
|
||||
@ -29,7 +29,7 @@ type Declaration struct {
|
||||
Transition string `json:"transition,omitempty" yaml:"transition,omitempty"`
|
||||
Attributes Resource `json:"attributes" yaml:"attributes"`
|
||||
Config ConfigName `json:"config,omitempty" yaml:"config,omitempty"`
|
||||
runtime luaruntime.LuaRunner
|
||||
// runtime luaruntime.LuaRunner
|
||||
document *Document
|
||||
configBlock *config.Block
|
||||
}
|
||||
@ -76,7 +76,7 @@ func (d *Declaration) Clone() *Declaration {
|
||||
Type: d.Type,
|
||||
Transition: d.Transition,
|
||||
Attributes: d.Attributes.Clone(),
|
||||
runtime: luaruntime.New(),
|
||||
//runtime: luaruntime.New(),
|
||||
Config: d.Config,
|
||||
}
|
||||
}
|
||||
@ -89,6 +89,19 @@ func (d *Declaration) LoadDecl(yamlResourceDeclaration string) error {
|
||||
return codec.NewYAMLStringDecoder(yamlResourceDeclaration).Decode(d)
|
||||
}
|
||||
|
||||
func (d *Declaration) JSON() ([]byte, error) {
|
||||
return json.Marshal(d)
|
||||
}
|
||||
|
||||
func (d *Declaration) Validate() (err error) {
|
||||
var declarationJson []byte
|
||||
if declarationJson, err = d.JSON(); err == nil {
|
||||
s := NewSchema(fmt.Sprintf("%s-declaration", d.Type))
|
||||
err = s.Validate(string(declarationJson))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Declaration) NewResource() error {
|
||||
uri := fmt.Sprintf("%s://", d.Type)
|
||||
newResource, err := ResourceTypes.New(uri)
|
||||
|
@ -58,12 +58,12 @@ func TestNewResourceDeclarationType(t *testing.T) {
|
||||
`, file)
|
||||
|
||||
resourceDeclaration := NewDeclaration()
|
||||
assert.NotEqual(t, nil, resourceDeclaration)
|
||||
assert.NotNil(t, resourceDeclaration)
|
||||
|
||||
e := resourceDeclaration.LoadDecl(decl)
|
||||
assert.Nil(t, e)
|
||||
assert.Equal(t, TypeName("file"), resourceDeclaration.Type)
|
||||
assert.NotEqual(t, nil, resourceDeclaration.Attributes)
|
||||
assert.NotNil(t, resourceDeclaration.Attributes)
|
||||
}
|
||||
|
||||
func TestDeclarationNewResource(t *testing.T) {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v3"
|
||||
"encoding/json"
|
||||
_ "log"
|
||||
"net/url"
|
||||
_ "os"
|
||||
@ -15,19 +16,18 @@ _ "strings"
|
||||
"io"
|
||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||
"decl/internal/codec"
|
||||
"decl/internal/command"
|
||||
)
|
||||
|
||||
type Exec struct {
|
||||
stater machine.Stater `yaml:"-" json:"-"`
|
||||
Id string `yaml:"id" json:"id"`
|
||||
CreateTemplate Command `yaml:"create" json:"create"`
|
||||
ReadTemplate Command `yaml:"read" json:"read"`
|
||||
UpdateTemplate Command `yaml:"update" json:"update"`
|
||||
DeleteTemplate Command `yaml:"delete" json:"delete"`
|
||||
Id string `yaml:"id,omitempty" json:"id,omitempty"`
|
||||
CreateTemplate *command.Command `yaml:"create,omitempty" json:"create,omitempty"`
|
||||
ReadTemplate *command.Command `yaml:"read,omitempty" json:"read,omitempty"`
|
||||
UpdateTemplate *command.Command `yaml:"update,omitempty" json:"update,omitempty"`
|
||||
DeleteTemplate *command.Command `yaml:"delete,omitempty" json:"delete,omitempty"`
|
||||
|
||||
config ConfigurationValueGetter
|
||||
// state attributes
|
||||
State string `yaml:"state"`
|
||||
Resources ResourceMapper `yaml:"-" json:"-"`
|
||||
}
|
||||
|
||||
@ -53,14 +53,12 @@ func (x *Exec) Clone() Resource {
|
||||
ReadTemplate: x.ReadTemplate,
|
||||
UpdateTemplate: x.UpdateTemplate,
|
||||
DeleteTemplate: x.DeleteTemplate,
|
||||
State: x.State,
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Exec) StateMachine() machine.Stater {
|
||||
if x.stater == nil {
|
||||
x.stater = ProcessMachine(x)
|
||||
|
||||
}
|
||||
return x.stater
|
||||
}
|
||||
@ -89,8 +87,13 @@ func (x *Exec) ResolveId(ctx context.Context) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Exec) Validate() error {
|
||||
return fmt.Errorf("failed")
|
||||
func (x *Exec) Validate() (err error) {
|
||||
var execJson []byte
|
||||
if execJson, err = x.JSON(); err == nil {
|
||||
s := NewSchema(x.Type())
|
||||
err = s.Validate(string(execJson))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (x *Exec) Apply() error {
|
||||
@ -108,9 +111,7 @@ func (x *Exec) Notify(m *machine.EventMessage) {
|
||||
return
|
||||
}
|
||||
}
|
||||
x.State = "absent"
|
||||
case "present":
|
||||
x.State = "present"
|
||||
}
|
||||
case machine.EXITSTATEEVENT:
|
||||
}
|
||||
@ -124,12 +125,21 @@ func (x *Exec) LoadDecl(yamlResourceDeclaration string) error {
|
||||
return codec.NewYAMLStringDecoder(yamlResourceDeclaration).Decode(x)
|
||||
}
|
||||
|
||||
func (x *Exec) JSON() ([]byte, error) {
|
||||
return json.Marshal(x)
|
||||
}
|
||||
|
||||
func (x *Exec) Type() string { return "exec" }
|
||||
|
||||
func (x *Exec) Create(ctx context.Context) error {
|
||||
return nil
|
||||
func (x *Exec) Create(ctx context.Context) (err error) {
|
||||
x.CreateTemplate.Defaults()
|
||||
if _, err = x.CreateTemplate.Execute(x); err == nil {
|
||||
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (x *Exec) Read(ctx context.Context) ([]byte, error) {
|
||||
x.ReadTemplate.Defaults()
|
||||
return yaml.Marshal(x)
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved
|
||||
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
@ -15,6 +17,7 @@ import (
|
||||
_ "os"
|
||||
_ "strings"
|
||||
"testing"
|
||||
"decl/internal/command"
|
||||
)
|
||||
|
||||
func TestNewExecResource(t *testing.T) {
|
||||
@ -38,6 +41,17 @@ func TestReadExecError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateExec(t *testing.T) {
|
||||
x := NewExec()
|
||||
decl := `
|
||||
create:
|
||||
path: go
|
||||
args:
|
||||
- install
|
||||
- golang.org/x/vuln/cmd/govulncheck@latest
|
||||
`
|
||||
assert.Nil(t, x.LoadDecl(decl))
|
||||
assert.Equal(t, "go", x.CreateTemplate.Path)
|
||||
assert.Equal(t, command.CommandArg("golang.org/x/vuln/cmd/govulncheck@latest"), x.CreateTemplate.Args[1])
|
||||
}
|
||||
|
||||
func TestExecSetURI(t *testing.T) {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"gopkg.in/yaml.v3"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/url"
|
||||
@ -38,7 +39,6 @@ const (
|
||||
SocketFile FileType = "socket"
|
||||
)
|
||||
|
||||
var ErrInvalidResourceURI error = errors.New("Invalid resource URI")
|
||||
var ErrInvalidFileInfo error = errors.New("Invalid FileInfo")
|
||||
var ErrInvalidFileMode error = errors.New("Invalid Mode")
|
||||
var ErrInvalidFileOwner error = errors.New("Unknown User")
|
||||
@ -149,7 +149,9 @@ func (f *File) Notify(m *machine.EventMessage) {
|
||||
}
|
||||
} else {
|
||||
f.State = "absent"
|
||||
panic(readErr)
|
||||
if ! errors.Is(readErr, ErrResourceStateAbsent) {
|
||||
panic(readErr)
|
||||
}
|
||||
}
|
||||
case "start_create":
|
||||
if e := f.Create(ctx); e == nil {
|
||||
@ -187,7 +189,7 @@ func (f *File) SetURI(uri string) error {
|
||||
resourceUri, e := url.Parse(uri)
|
||||
if e == nil {
|
||||
if resourceUri.Scheme == "file" {
|
||||
f.Path = filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI())
|
||||
f.Path = filepath.Join(resourceUri.Hostname(), resourceUri.Path)
|
||||
if err := f.NormalizePath(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -202,8 +204,17 @@ func (f *File) UseConfig(config ConfigurationValueGetter) {
|
||||
f.config = config
|
||||
}
|
||||
|
||||
func (f *File) Validate() error {
|
||||
return fmt.Errorf("failed")
|
||||
func (f *File) JSON() ([]byte, error) {
|
||||
return json.Marshal(f)
|
||||
}
|
||||
|
||||
func (f *File) Validate() (err error) {
|
||||
var fileJson []byte
|
||||
if fileJson, err = f.JSON(); err == nil {
|
||||
s := NewSchema(f.Type())
|
||||
err = s.Validate(string(fileJson))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *File) Apply() error {
|
||||
@ -427,7 +438,7 @@ func (f *File) Read(ctx context.Context) ([]byte, error) {
|
||||
|
||||
statErr := f.ReadStat()
|
||||
if statErr != nil {
|
||||
return nil, statErr
|
||||
return nil, fmt.Errorf("%w - %w", ErrResourceStateAbsent, statErr)
|
||||
}
|
||||
|
||||
switch f.FileType {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
"os/user"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
func TestNewFileResource(t *testing.T) {
|
||||
@ -108,7 +109,7 @@ func TestReadFileError(t *testing.T) {
|
||||
assert.NotEqual(t, nil, f)
|
||||
f.Path = file
|
||||
_, e := f.Read(ctx)
|
||||
assert.True(t, os.IsNotExist(e))
|
||||
assert.ErrorIs(t, e, fs.ErrNotExist)
|
||||
assert.Equal(t, "absent", f.State)
|
||||
}
|
||||
|
||||
@ -448,3 +449,47 @@ func TestFileContentRef(t *testing.T) {
|
||||
assert.Nil(t, stater.Trigger("delete"))
|
||||
assert.NoFileExists(t, file, nil)
|
||||
}
|
||||
|
||||
func TestFilePathURI(t *testing.T) {
|
||||
// file, _ := filepath.Abs(filepath.Join(TempDir, "foo.txt"))
|
||||
|
||||
decl := fmt.Sprintf(`
|
||||
path: "%s"
|
||||
owner: "%s"
|
||||
group: "%s"
|
||||
mode: "0600"
|
||||
content: |-
|
||||
test line 1
|
||||
test line 2
|
||||
`, "", ProcessTestUserName, ProcessTestGroupName)
|
||||
|
||||
f := NewFile()
|
||||
e := f.LoadDecl(decl)
|
||||
assert.Nil(t, e)
|
||||
assert.Equal(t, "", f.Path)
|
||||
assert.ErrorContains(t, f.Validate(), "path: String length must be greater than or equal to 1")
|
||||
}
|
||||
|
||||
func TestFileAbsent(t *testing.T) {
|
||||
file, _ := filepath.Abs(filepath.Join(TempDir, "testabsentstate.txt"))
|
||||
|
||||
decl := fmt.Sprintf(`
|
||||
path: "%s"
|
||||
owner: "%s"
|
||||
group: "%s"
|
||||
mode: "0600"
|
||||
filetype: "regular"
|
||||
`, file, ProcessTestUserName, ProcessTestGroupName)
|
||||
|
||||
f := NewFile()
|
||||
stater := f.StateMachine()
|
||||
e := f.LoadDecl(decl)
|
||||
assert.Equal(t, nil, e)
|
||||
assert.Equal(t, ProcessTestUserName, f.Owner)
|
||||
|
||||
err := stater.Trigger("read")
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "absent", f.State)
|
||||
|
||||
}
|
||||
|
@ -115,7 +115,9 @@ func (p *Package) Notify(m *machine.EventMessage) {
|
||||
}
|
||||
} else {
|
||||
p.State = "absent"
|
||||
panic(readErr)
|
||||
if ! errors.Is(readErr, ErrResourceStateAbsent) {
|
||||
panic(readErr)
|
||||
}
|
||||
}
|
||||
case "start_create":
|
||||
if e := p.Create(ctx); e == nil {
|
||||
@ -124,6 +126,12 @@ func (p *Package) Notify(m *machine.EventMessage) {
|
||||
}
|
||||
}
|
||||
p.State = "absent"
|
||||
case "start_update":
|
||||
if e := p.Update(ctx); e == nil {
|
||||
if triggerErr := p.StateMachine().Trigger("updated"); triggerErr == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
case "start_delete":
|
||||
if deleteErr := p.Delete(ctx); deleteErr == nil {
|
||||
if triggerErr := p.StateMachine().Trigger("deleted"); triggerErr == nil {
|
||||
@ -138,7 +146,7 @@ func (p *Package) Notify(m *machine.EventMessage) {
|
||||
}
|
||||
case "absent":
|
||||
p.State = "absent"
|
||||
case "present", "created", "read":
|
||||
case "present", "created", "updated", "read":
|
||||
p.State = "present"
|
||||
}
|
||||
case machine.EXITSTATEEVENT:
|
||||
@ -187,7 +195,46 @@ func (p *Package) Validate() error {
|
||||
}
|
||||
|
||||
func (p *Package) ResolveId(ctx context.Context) string {
|
||||
return ""
|
||||
slog.Info("Package.ResolveId()", "name", p.Name, "machine.state", p.StateMachine().CurrentState())
|
||||
/*
|
||||
imageInspect, _, err := p.apiClient.ImageInspectWithRaw(ctx, p.Name)
|
||||
if err != nil {
|
||||
triggerResult := p.StateMachine().Trigger("notexists")
|
||||
slog.Info("ContainerImage.ResolveId()", "name", p.Name, "machine.state", p.StateMachine().CurrentState(), "resource.state", p.State, "trigger.error", triggerResult)
|
||||
panic(fmt.Errorf("%w: %s %s", err, p.Type(), p.Name))
|
||||
}
|
||||
slog.Info("ContainerImage.ResolveId()", "name", c.Name, "machine.state", c.StateMachine().CurrentState(), "resource.state", c.State)
|
||||
c.Id = imageInspect.ID
|
||||
if c.Id != "" {
|
||||
if triggerErr := c.StateMachine().Trigger("exists"); triggerErr != nil {
|
||||
panic(fmt.Errorf("%w: %s %s", triggerErr, c.Type(), c.Name))
|
||||
}
|
||||
slog.Info("ContainerImage.ResolveId() trigger created", "machine", c.StateMachine(), "state", c.StateMachine().CurrentState())
|
||||
} else {
|
||||
if triggerErr := c.StateMachine().Trigger("notexists"); triggerErr != nil {
|
||||
panic(fmt.Errorf("%w: %s %s", triggerErr, c.Type(), c.Name))
|
||||
}
|
||||
slog.Info("ContainerImage.ResolveId()", "name", c.Name, "machine.state", c.StateMachine().CurrentState(), "resource.state", c.State)
|
||||
}
|
||||
return c.Id
|
||||
*/
|
||||
if p.ReadCommand.Exists() {
|
||||
if _, err := p.ReadCommand.Execute(p); err != nil {
|
||||
if triggerResult := p.StateMachine().Trigger("notexists"); triggerResult != nil {
|
||||
panic(fmt.Errorf("%w: %s %s", err, p.Type(), p.Name))
|
||||
}
|
||||
}
|
||||
/*
|
||||
exErr := p.ReadCommand.Extractor(out, p)
|
||||
if exErr != nil {
|
||||
return nil, exErr
|
||||
}
|
||||
return yaml.Marshal(p)
|
||||
} else {
|
||||
return nil, ErrUnsupportedPackageType
|
||||
*/
|
||||
}
|
||||
return p.Name
|
||||
}
|
||||
|
||||
func (p *Package) Create(ctx context.Context) error {
|
||||
@ -202,6 +249,10 @@ func (p *Package) Create(ctx context.Context) error {
|
||||
return e
|
||||
}
|
||||
|
||||
func (p *Package) Update(ctx context.Context) error {
|
||||
return p.Create(ctx)
|
||||
}
|
||||
|
||||
func (p *Package) Delete(ctx context.Context) error {
|
||||
_, err := p.DeleteCommand.Execute(p)
|
||||
if err != nil {
|
||||
@ -235,20 +286,24 @@ func (p *Package) LoadDecl(yamlResourceDeclaration string) error {
|
||||
|
||||
func (p *Package) Type() string { return "package" }
|
||||
|
||||
func (p *Package) Read(ctx context.Context) ([]byte, error) {
|
||||
func (p *Package) Read(ctx context.Context) (resourceYaml []byte, err error) {
|
||||
if p.ReadCommand.Exists() {
|
||||
out, err := p.ReadCommand.Execute(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var out []byte
|
||||
out, err = p.ReadCommand.Execute(p)
|
||||
if err == nil {
|
||||
err = p.ReadCommand.Extractor(out, p)
|
||||
} else {
|
||||
err = fmt.Errorf("%w - %w", ErrResourceStateAbsent, err)
|
||||
}
|
||||
exErr := p.ReadCommand.Extractor(out, p)
|
||||
if exErr != nil {
|
||||
return nil, exErr
|
||||
}
|
||||
return yaml.Marshal(p)
|
||||
} else {
|
||||
return nil, ErrUnsupportedPackageType
|
||||
err = ErrUnsupportedPackageType
|
||||
}
|
||||
var yamlErr error
|
||||
resourceYaml, yamlErr = yaml.Marshal(p)
|
||||
if err == nil {
|
||||
err = yamlErr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Package) UnmarshalJSON(data []byte) error {
|
||||
|
@ -4,17 +4,17 @@ package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "encoding/json"
|
||||
_ "fmt"
|
||||
_ "encoding/json"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
_ "gopkg.in/yaml.v3"
|
||||
_ "io"
|
||||
_ "gopkg.in/yaml.v3"
|
||||
_ "io"
|
||||
"log/slog"
|
||||
_ "net/http"
|
||||
_ "net/http/httptest"
|
||||
_ "net/url"
|
||||
_ "os"
|
||||
_ "strings"
|
||||
_ "net/http"
|
||||
_ "net/http/httptest"
|
||||
_ "net/url"
|
||||
_ "os"
|
||||
_ "strings"
|
||||
"testing"
|
||||
"decl/internal/command"
|
||||
)
|
||||
@ -95,6 +95,34 @@ Version: 1.2.2
|
||||
}
|
||||
|
||||
func TestReadPackageError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
expected := fmt.Sprintf(`
|
||||
name: missing
|
||||
state: absent
|
||||
type: %s
|
||||
`, SystemPackageType)
|
||||
|
||||
decl := `
|
||||
name: missing
|
||||
type: apt
|
||||
`
|
||||
p := NewPackage()
|
||||
assert.NotNil(t, p)
|
||||
loadErr := p.LoadDecl(decl)
|
||||
assert.Nil(t, loadErr)
|
||||
p.ReadCommand = NewAptReadCommand()
|
||||
/*
|
||||
p.ReadCommand.Executor = func(value any) ([]byte, error) {
|
||||
return []byte(``), fmt.Errorf("exit status 1 dpkg-query: package 'makef' is not installed and no information is available\nUse dpkg --info (= dpkg-deb --info) to examine archive files.\n")
|
||||
}
|
||||
*/
|
||||
p.ResolveId(ctx)
|
||||
yaml, readErr := p.Read(ctx)
|
||||
assert.ErrorIs(t, readErr, ErrResourceStateAbsent)
|
||||
assert.YAMLEq(t, expected, string(yaml))
|
||||
slog.Info("read()", "yaml", yaml)
|
||||
assert.Equal(t, "", p.Version)
|
||||
assert.Nil(t, p.Validate())
|
||||
}
|
||||
|
||||
func TestCreatePackage(t *testing.T) {
|
||||
|
@ -161,6 +161,9 @@ func (k *PKI) Notify(m *machine.EventMessage) {
|
||||
|
||||
func (k *PKI) URI() string {
|
||||
u := k.PrivateKeyRef.Parse()
|
||||
if u.Scheme == "file" || u.Scheme == "pki" {
|
||||
return fmt.Sprintf("pki://%s", filepath.Join(u.Hostname(), u.Path))
|
||||
}
|
||||
return fmt.Sprintf("pki://%s", filepath.Join(u.Hostname(), u.RequestURI()))
|
||||
}
|
||||
|
||||
@ -168,7 +171,7 @@ func (k *PKI) SetURI(uri string) error {
|
||||
resourceUri, e := url.Parse(uri)
|
||||
if e == nil {
|
||||
if resourceUri.Scheme == "pki" {
|
||||
k.PrivateKeyRef = ResourceReference(fmt.Sprintf("pki://%s", filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI())))
|
||||
k.PrivateKeyRef = ResourceReference(fmt.Sprintf("pki://%s", filepath.Join(resourceUri.Hostname(), resourceUri.Path)))
|
||||
} else {
|
||||
e = fmt.Errorf("%w: %s is not a cert", ErrInvalidResourceURI, uri)
|
||||
}
|
||||
|
@ -12,6 +12,13 @@ import (
|
||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||
"decl/internal/transport"
|
||||
"log/slog"
|
||||
"errors"
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
ErrInvalidResourceURI error = errors.New("Invalid resource URI")
|
||||
ErrResourceStateAbsent = errors.New("Resource state absent")
|
||||
)
|
||||
|
||||
type ResourceReference string
|
||||
|
31
internal/resource/schemas/command.schema.json
Normal file
31
internal/resource/schemas/command.schema.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"$id": "command.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "command",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "command path",
|
||||
"minLength": 1
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"description": "list of command args",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"type": "boolean",
|
||||
"description": "split command line args by space"
|
||||
},
|
||||
"failonerror": {
|
||||
"type": "boolean",
|
||||
"description": "Generate an error if the command fails",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$id": "container-declaration.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "declaration",
|
||||
"title": "container-declaration",
|
||||
"type": "object",
|
||||
"required": [ "type", "attributes" ],
|
||||
"properties": {
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$id": "container-image-declaration.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "declaration",
|
||||
"title": "container-image-declaration",
|
||||
"type": "object",
|
||||
"required": [ "type", "attributes" ],
|
||||
"properties": {
|
||||
|
@ -9,6 +9,12 @@
|
||||
"name": {
|
||||
"type": "string",
|
||||
"pattern": "^(?:[-0-9A-Za-z_.]+((?::[0-9]+|)(?:/[-a-z0-9._]+/[-a-z0-9._]+))|)(?:/|)(?:[-a-z0-9._]+(?:/[-a-z0-9._]+|))(:(?:[-0-9A-Za-z_.]{1,127})|)$"
|
||||
},
|
||||
"Contextref": {
|
||||
"type": "string"
|
||||
},
|
||||
"Injectjx": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$id": "container-network-declaration.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "declaration",
|
||||
"title": "container-network-declaration",
|
||||
"type": "object",
|
||||
"required": [ "type", "attributes" ],
|
||||
"properties": {
|
||||
|
@ -3,19 +3,22 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "exec",
|
||||
"type": "object",
|
||||
"required": [ "create", "read" ],
|
||||
"properties": {
|
||||
"Id": {
|
||||
"type": "string"
|
||||
},
|
||||
"create": {
|
||||
"type": "string"
|
||||
"$ref": "command.schema.json"
|
||||
},
|
||||
"read": {
|
||||
"type": "string"
|
||||
"Read": {
|
||||
"$ref": "command.schema.json"
|
||||
},
|
||||
"update": {
|
||||
"type": "string"
|
||||
"Update": {
|
||||
"$ref": "command.schema.json"
|
||||
},
|
||||
"delete": {
|
||||
"type": "string"
|
||||
"Delete": {
|
||||
"$ref": "command.schema.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [ "create" ]
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$id": "group.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "group",
|
||||
"title": "group-declaration",
|
||||
"description": "A group account",
|
||||
"type": "object",
|
||||
"required": [ "name" ],
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$id": "pki-declaration.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "declaration",
|
||||
"title": "pki-declaration",
|
||||
"type": "object",
|
||||
"required": [ "type", "attributes" ],
|
||||
"properties": {
|
||||
|
@ -5,6 +5,5 @@
|
||||
"type": "string",
|
||||
"description": "Storage state transition",
|
||||
"enum": [ "absent", "present", "create", "read", "update", "delete" ]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$id": "user-declaration.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "declaration",
|
||||
"title": "user-declaration",
|
||||
"type": "object",
|
||||
"required": [ "type", "attributes" ],
|
||||
"properties": {
|
||||
|
@ -32,7 +32,7 @@ func NewDeclFile() *DeclFile {
|
||||
func init() {
|
||||
SourceTypes.Register([]string{"decl"}, func(u *url.URL) DocSource {
|
||||
t := NewDeclFile()
|
||||
t.Path,_ = filepath.Abs(filepath.Join(u.Hostname(), u.RequestURI()))
|
||||
t.Path,_ = filepath.Abs(filepath.Join(u.Hostname(), u.Path))
|
||||
t.transport,_ = transport.NewReader(u)
|
||||
return t
|
||||
})
|
||||
@ -40,7 +40,7 @@ func init() {
|
||||
SourceTypes.Register([]string{"yaml","yml","yaml.gz","yml.gz"}, func(u *url.URL) DocSource {
|
||||
t := NewDeclFile()
|
||||
if u.Scheme == "file" {
|
||||
fileAbsolutePath, _ := filepath.Abs(filepath.Join(u.Hostname(), u.RequestURI()))
|
||||
fileAbsolutePath, _ := filepath.Abs(filepath.Join(u.Hostname(), u.Path))
|
||||
t.Path = fileAbsolutePath
|
||||
} else {
|
||||
t.Path = filepath.Join(u.Hostname(), u.Path)
|
||||
|
@ -28,7 +28,7 @@ func NewDir() *Dir {
|
||||
func init() {
|
||||
SourceTypes.Register([]string{"file"}, func(u *url.URL) DocSource {
|
||||
t := NewDir()
|
||||
t.Path,_ = filepath.Abs(filepath.Join(u.Hostname(), u.RequestURI()))
|
||||
t.Path,_ = filepath.Abs(filepath.Join(u.Hostname(), u.Path))
|
||||
return t
|
||||
})
|
||||
|
||||
|
@ -38,7 +38,7 @@ func init() {
|
||||
SourceTypes.Register([]string{"tar.gz", "tgz"}, func(u *url.URL) DocSource {
|
||||
t := NewTar()
|
||||
if u.Scheme == "file" {
|
||||
fileAbsolutePath, _ := filepath.Abs(filepath.Join(u.Hostname(), u.RequestURI()))
|
||||
fileAbsolutePath, _ := filepath.Abs(filepath.Join(u.Hostname(), u.Path))
|
||||
t.Path = fileAbsolutePath
|
||||
} else {
|
||||
t.Path = filepath.Join(u.Hostname(), u.Path)
|
||||
|
@ -40,7 +40,7 @@ func NewFileDocTarget(u *url.URL, format string, gzip bool, fileUri bool) DocTar
|
||||
t.Format = format
|
||||
t.Gzip = gzip
|
||||
if fileUri {
|
||||
fileAbsolutePath, _ := filepath.Abs(filepath.Join(u.Hostname(), u.RequestURI()))
|
||||
fileAbsolutePath, _ := filepath.Abs(filepath.Join(u.Hostname(), u.Path))
|
||||
t.Path = fileAbsolutePath
|
||||
} else {
|
||||
t.Path = filepath.Join(u.Hostname(), u.Path)
|
||||
|
@ -43,7 +43,7 @@ func init() {
|
||||
TargetTypes.Register([]string{"tar.gz", "tgz"}, func(u *url.URL) DocTarget {
|
||||
t := NewTar()
|
||||
if u.Scheme == "file" {
|
||||
fileAbsolutePath, _ := filepath.Abs(filepath.Join(u.Hostname(), u.RequestURI()))
|
||||
fileAbsolutePath, _ := filepath.Abs(filepath.Join(u.Hostname(), u.Path))
|
||||
t.Path = fileAbsolutePath
|
||||
} else {
|
||||
t.Path = filepath.Join(u.Hostname(), u.Path)
|
||||
|
@ -1,116 +0,0 @@
|
||||
// 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 Buffer 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 NewBuffer(u *url.URL) (b *Buffer, err error) {
|
||||
b = &Buffer{
|
||||
uri: u,
|
||||
path: filepath.Join(u.Hostname(), u.RequestURI()),
|
||||
}
|
||||
b.extension()
|
||||
b.DetectGzip()
|
||||
|
||||
if b.path == "" || b.path == "-" {
|
||||
b.readHandle = os.Stdin
|
||||
b.writeHandle = os.Stdout
|
||||
} else {
|
||||
if b.readHandle, err = os.OpenFile(b.Path(), os.O_RDWR|os.O_CREATE, 0644); err != nil {
|
||||
return
|
||||
}
|
||||
b.writeHandle = b.readHandle
|
||||
}
|
||||
|
||||
if b.Gzip() {
|
||||
b.gzipWriter = gzip.NewWriter(b.writeHandle)
|
||||
if b.gzipReader, err = gzip.NewReader(b.readHandle); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Buffer) extension() {
|
||||
elements := strings.Split(b.path, ".")
|
||||
numberOfElements := len(elements)
|
||||
if numberOfElements > 2 {
|
||||
b.exttype = elements[numberOfElements - 2]
|
||||
b.fileext = elements[numberOfElements - 1]
|
||||
}
|
||||
b.exttype = elements[numberOfElements - 1]
|
||||
}
|
||||
|
||||
func (b *Buffer) DetectGzip() {
|
||||
b.gzip = (b.uri.Query().Get("gzip") == "true" || b.fileext == "gz")
|
||||
}
|
||||
|
||||
func (b *Buffer) URI() *url.URL {
|
||||
return b.uri
|
||||
}
|
||||
|
||||
func (b *Buffer) Path() string {
|
||||
return b.path
|
||||
}
|
||||
|
||||
func (b *Buffer) Signature() (documentSignature string) {
|
||||
if signatureResp, signatureErr := os.Open(fmt.Sprintf("%s.sig", b.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 (b *Buffer) ContentType() string {
|
||||
return b.exttype
|
||||
}
|
||||
|
||||
func (b *Buffer) SetGzip(gzip bool) {
|
||||
b.gzip = gzip
|
||||
}
|
||||
|
||||
func (b *Buffer) Gzip() bool {
|
||||
return b.gzip
|
||||
}
|
||||
|
||||
func (b *Buffer) Reader() io.ReadCloser {
|
||||
if b.Gzip() {
|
||||
return b.gzipReader
|
||||
}
|
||||
return b.readHandle
|
||||
}
|
||||
|
||||
func (b *Buffer) Writer() io.WriteCloser {
|
||||
if b.Gzip() {
|
||||
return b.gzipWriter
|
||||
}
|
||||
return b.writeHandle
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
// 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 TransportBufferTestFile = fmt.Sprintf("%s/foo", TempDir)
|
||||
|
||||
func TestNewTransportBufferReader(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 := NewBuffer(u)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, file.Path(), path)
|
||||
}
|
||||
|
||||
func TestNewTransportBufferReaderExtension(t *testing.T) {
|
||||
u, e := url.Parse(fmt.Sprintf("file://%s.yaml", TransportBufferTestFile))
|
||||
assert.Nil(t, e)
|
||||
|
||||
b := &Buffer{
|
||||
uri: u,
|
||||
path: filepath.Join(u.Hostname(), u.RequestURI()),
|
||||
}
|
||||
b.extension()
|
||||
assert.Equal(t, b.exttype, "yaml")
|
||||
}
|
@ -26,7 +26,7 @@ type File struct {
|
||||
}
|
||||
|
||||
func FilePath(u *url.URL) string {
|
||||
return filepath.Join(u.Hostname(), u.RequestURI())
|
||||
return filepath.Join(u.Hostname(), u.Path)
|
||||
}
|
||||
|
||||
func FileExists(u *url.URL) bool {
|
||||
|
@ -32,7 +32,7 @@ func TestNewTransportFileReaderExtension(t *testing.T) {
|
||||
|
||||
f := &File{
|
||||
uri: u,
|
||||
path: filepath.Join(u.Hostname(), u.RequestURI()),
|
||||
path: filepath.Join(u.Hostname(), u.Path),
|
||||
}
|
||||
f.extension()
|
||||
assert.Equal(t, f.exttype, "yaml")
|
||||
|
@ -86,9 +86,10 @@ func NewWriterURI(uri string) (writer *Writer, e error) {
|
||||
}
|
||||
|
||||
func ExistsURI(uri string) bool {
|
||||
var u *url.URL
|
||||
u, _ = url.Parse(uri)
|
||||
return Exists(u)
|
||||
if u, e := url.Parse(uri); e == nil {
|
||||
return Exists(u)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Exists(u *url.URL) bool {
|
||||
|
@ -65,6 +65,16 @@ func TestTransportReaderContentType(t *testing.T) {
|
||||
assert.Equal(t, reader.ContentType(), "yaml")
|
||||
}
|
||||
|
||||
func TestTransportReaderDir(t *testing.T) {
|
||||
u, e := url.Parse(fmt.Sprintf("file://%s", TempDir))
|
||||
assert.Nil(t, e)
|
||||
|
||||
reader, err := NewReader(u)
|
||||
assert.ErrorContains(t, err, "is a directory")
|
||||
assert.True(t, reader.Exists())
|
||||
assert.NotNil(t, reader)
|
||||
}
|
||||
|
||||
func TestTransportWriter(t *testing.T) {
|
||||
path := fmt.Sprintf("%s/writefoo", TempDir)
|
||||
u, e := url.Parse(fmt.Sprintf("file://%s", path))
|
||||
|
@ -18,18 +18,19 @@ The `types` package provides a generic method of registering a type factory.
|
||||
|
||||
var (
|
||||
ErrUnknownType = errors.New("Unknown type")
|
||||
ErrInvalidProduct = errors.New("Invalid product")
|
||||
)
|
||||
|
||||
//type Name[Registry any] string //`json:"type"`
|
||||
|
||||
type Factory[Product any] func(*url.URL) Product
|
||||
type RegistryTypeMap[Product any] map[string]Factory[Product]
|
||||
type Factory[Product comparable] func(*url.URL) Product
|
||||
type RegistryTypeMap[Product comparable] map[string]Factory[Product]
|
||||
|
||||
type Types[Product any] struct {
|
||||
type Types[Product comparable] struct {
|
||||
registry RegistryTypeMap[Product]
|
||||
}
|
||||
|
||||
func New[Product any]() *Types[Product] {
|
||||
func New[Product comparable]() *Types[Product] {
|
||||
return &Types[Product]{registry: make(map[string]Factory[Product])}
|
||||
}
|
||||
|
||||
@ -70,7 +71,11 @@ func (t *Types[Product]) New(uri string) (result Product, err error) {
|
||||
}
|
||||
|
||||
if r, ok := t.registry[u.Scheme]; ok {
|
||||
return r(u), nil
|
||||
if result = r(u); result != any(nil) {
|
||||
return result, nil
|
||||
} else {
|
||||
return result, fmt.Errorf("%w: factory failed creating %s", ErrInvalidProduct, uri)
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("%w: %s", ErrUnknownType, u.Scheme)
|
||||
return
|
||||
|
@ -26,6 +26,7 @@ type MockContainerClient struct {
|
||||
InjectImagePull func(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error)
|
||||
InjectImageInspectWithRaw func(ctx context.Context, imageID string) (types.ImageInspect, []byte, error)
|
||||
InjectImageRemove func(ctx context.Context, imageID string, options image.RemoveOptions) ([]image.DeleteResponse, error)
|
||||
InjectImageBuild func(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
|
||||
InjectClose func() error
|
||||
}
|
||||
|
||||
@ -41,6 +42,10 @@ func (m *MockContainerClient) ImagePull(ctx context.Context, refStr string, opti
|
||||
return m.InjectImagePull(ctx, refStr, options)
|
||||
}
|
||||
|
||||
func (m *MockContainerClient) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
|
||||
return m.InjectImageBuild(ctx, buildContext, options)
|
||||
}
|
||||
|
||||
func (m *MockContainerClient) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) {
|
||||
return m.InjectImageInspectWithRaw(ctx, imageID)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user