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
|
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')'"
|
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')
|
VERSION?=$(shell git describe --tags | sed -e 's/^v//' -e 's/-/_/g')
|
||||||
.PHONY=jx-cli
|
.PHONY=jx-cli
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ ubuntu-deps:
|
|||||||
deb: ubuntu-deps
|
deb: ubuntu-deps
|
||||||
:
|
:
|
||||||
run:
|
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:
|
clean:
|
||||||
go clean -modcache
|
go clean -modcache
|
||||||
rm jx
|
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
|
go 1.22.5
|
||||||
|
|
||||||
require (
|
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
|
gitea.rosskeen.house/rosskeen.house/machine v0.0.0-20240520193117-1835255b6d02
|
||||||
github.com/docker/docker v27.0.3+incompatible
|
github.com/docker/docker v27.0.3+incompatible
|
||||||
github.com/docker/go-connections v0.5.0
|
github.com/docker/go-connections v0.5.0
|
||||||
|
@ -39,6 +39,13 @@ type Command struct {
|
|||||||
|
|
||||||
func NewCommand() *Command {
|
func NewCommand() *Command {
|
||||||
c := &Command{ Split: true, FailOnError: true }
|
c := &Command{ Split: true, FailOnError: true }
|
||||||
|
c.Defaults()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) Defaults() {
|
||||||
|
c.Split = true
|
||||||
|
c.FailOnError = true
|
||||||
c.CommandExists = func() error {
|
c.CommandExists = func() error {
|
||||||
if _, err := exec.LookPath(c.Path); err != nil {
|
if _, err := exec.LookPath(c.Path); err != nil {
|
||||||
return fmt.Errorf("%w - %w", ErrUnknownCommand, err)
|
return fmt.Errorf("%w - %w", ErrUnknownCommand, err)
|
||||||
@ -82,7 +89,6 @@ func NewCommand() *Command {
|
|||||||
}
|
}
|
||||||
return stdOutOutput, waitErr
|
return stdOutOutput, waitErr
|
||||||
}
|
}
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) Load(r io.Reader) error {
|
func (c *Command) Load(r io.Reader) error {
|
||||||
|
@ -33,7 +33,7 @@ func NewConfigFile() *ConfigFile {
|
|||||||
func NewConfigFileFromURI(u *url.URL) *ConfigFile {
|
func NewConfigFileFromURI(u *url.URL) *ConfigFile {
|
||||||
t := NewConfigFile()
|
t := NewConfigFile()
|
||||||
if u.Scheme == "file" {
|
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 {
|
} else {
|
||||||
t.Path = filepath.Join(u.Hostname(), u.Path)
|
t.Path = filepath.Join(u.Hostname(), u.Path)
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ func init() {
|
|||||||
ConfigSourceTypes.Register([]string{"fs"}, func(u *url.URL) ConfigSource {
|
ConfigSourceTypes.Register([]string{"fs"}, func(u *url.URL) ConfigSource {
|
||||||
|
|
||||||
t := NewConfigFS(nil)
|
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)
|
t.fsys = os.DirFS(t.Path)
|
||||||
return t
|
return t
|
||||||
})
|
})
|
||||||
|
@ -26,6 +26,7 @@ type ContainerImageClient interface {
|
|||||||
ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error)
|
ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error)
|
||||||
ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error)
|
ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error)
|
||||||
ImageRemove(ctx context.Context, imageID string, options image.RemoveOptions) ([]image.DeleteResponse, 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
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +41,9 @@ type ContainerImage struct {
|
|||||||
Size int64 `json:"size" yaml:"size"`
|
Size int64 `json:"size" yaml:"size"`
|
||||||
Author string `json:"author,omitempty" yaml:"author,omitempty"`
|
Author string `json:"author,omitempty" yaml:"author,omitempty"`
|
||||||
Comment string `json:"comment,omitempty" yaml:"comment,omitempty"`
|
Comment string `json:"comment,omitempty" yaml:"comment,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"`
|
State string `yaml:"state,omitempty" json:"state,omitempty"`
|
||||||
|
|
||||||
config ConfigurationValueGetter
|
config ConfigurationValueGetter
|
||||||
@ -67,6 +71,7 @@ func NewContainerImage(containerClientApi ContainerImageClient) *ContainerImage
|
|||||||
}
|
}
|
||||||
return &ContainerImage{
|
return &ContainerImage{
|
||||||
apiClient: apiClient,
|
apiClient: apiClient,
|
||||||
|
InjectJX: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +90,7 @@ func (c *ContainerImage) Clone() Resource {
|
|||||||
Size: c.Size,
|
Size: c.Size,
|
||||||
Author: c.Author,
|
Author: c.Author,
|
||||||
Comment: c.Comment,
|
Comment: c.Comment,
|
||||||
|
InjectJX: c.InjectJX,
|
||||||
State: c.State,
|
State: c.State,
|
||||||
apiClient: c.apiClient,
|
apiClient: c.apiClient,
|
||||||
}
|
}
|
||||||
@ -196,6 +202,17 @@ func (c *ContainerImage) Notify(m *machine.EventMessage) {
|
|||||||
c.State = "absent"
|
c.State = "absent"
|
||||||
panic(createErr)
|
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":
|
case "start_delete":
|
||||||
if deleteErr := c.Delete(ctx); deleteErr == nil {
|
if deleteErr := c.Delete(ctx); deleteErr == nil {
|
||||||
if triggerErr := c.StateMachine().Trigger("deleted"); triggerErr == 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 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ContainerImage) Update(ctx context.Context) error {
|
||||||
|
return c.Create(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ContainerImage) Pull(ctx context.Context) error {
|
func (c *ContainerImage) Pull(ctx context.Context) error {
|
||||||
out, err := c.apiClient.ImagePull(ctx, c.Name, image.PullOptions{})
|
out, err := c.apiClient.ImagePull(ctx, c.Name, image.PullOptions{})
|
||||||
slog.Info("ContainerImage.Pull()", "name", c.Name, "error", err)
|
slog.Info("ContainerImage.Pull()", "name", c.Name, "error", err)
|
||||||
@ -345,3 +390,5 @@ func (c *ContainerImage) ResolveId(ctx context.Context) string {
|
|||||||
}
|
}
|
||||||
return c.Id
|
return c.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"decl/tests/mocks"
|
"decl/tests/mocks"
|
||||||
_ "encoding/json"
|
_ "encoding/json"
|
||||||
_ "fmt"
|
"fmt"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -96,33 +96,24 @@ func TestReadContainerImage(t *testing.T) {
|
|||||||
assert.Greater(t, len(resourceYaml), 0)
|
assert.Greater(t, len(resourceYaml), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
func TestCreateContainerImage(t *testing.T) {
|
func TestCreateContainerImage(t *testing.T) {
|
||||||
m := &mocks.MockContainerClient{
|
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) {
|
InjectImageBuild: func(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
|
||||||
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
return types.ImageBuildResponse{Body: io.NopCloser(strings.NewReader("image built")) }, nil
|
||||||
},
|
|
||||||
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
decl := `
|
decl := fmt.Sprintf(`
|
||||||
name: "testcontainer"
|
name: "testcontainerimage"
|
||||||
image: "alpine"
|
image: "alpine"
|
||||||
state: present
|
contextref: file://%s
|
||||||
`
|
`, "")
|
||||||
c := NewContainerImage(m)
|
c := NewContainerImage(m)
|
||||||
|
stater := c.StateMachine()
|
||||||
|
|
||||||
e := c.LoadDecl(decl)
|
e := c.LoadDecl(decl)
|
||||||
assert.Equal(t, nil, e)
|
assert.Equal(t, nil, e)
|
||||||
assert.Equal(t, "testcontainer", c.Name)
|
assert.Equal(t, "testcontainerimage", c.Name)
|
||||||
|
|
||||||
applyErr := c.Apply()
|
assert.Nil(t, stater.Trigger("create"))
|
||||||
assert.Equal(t, nil, applyErr)
|
|
||||||
|
|
||||||
c.State = "absent"
|
|
||||||
|
|
||||||
applyDeleteErr := c.Apply()
|
|
||||||
assert.Equal(t, nil, applyDeleteErr)
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
@ -11,7 +11,7 @@ _ "errors"
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
_ "gitea.rosskeen.house/rosskeen.house/machine"
|
_ "gitea.rosskeen.house/rosskeen.house/machine"
|
||||||
"gitea.rosskeen.house/pylon/luaruntime"
|
//_ "gitea.rosskeen.house/pylon/luaruntime"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"decl/internal/config"
|
"decl/internal/config"
|
||||||
)
|
)
|
||||||
@ -29,7 +29,7 @@ type Declaration struct {
|
|||||||
Transition string `json:"transition,omitempty" yaml:"transition,omitempty"`
|
Transition string `json:"transition,omitempty" yaml:"transition,omitempty"`
|
||||||
Attributes Resource `json:"attributes" yaml:"attributes"`
|
Attributes Resource `json:"attributes" yaml:"attributes"`
|
||||||
Config ConfigName `json:"config,omitempty" yaml:"config,omitempty"`
|
Config ConfigName `json:"config,omitempty" yaml:"config,omitempty"`
|
||||||
runtime luaruntime.LuaRunner
|
// runtime luaruntime.LuaRunner
|
||||||
document *Document
|
document *Document
|
||||||
configBlock *config.Block
|
configBlock *config.Block
|
||||||
}
|
}
|
||||||
@ -76,7 +76,7 @@ func (d *Declaration) Clone() *Declaration {
|
|||||||
Type: d.Type,
|
Type: d.Type,
|
||||||
Transition: d.Transition,
|
Transition: d.Transition,
|
||||||
Attributes: d.Attributes.Clone(),
|
Attributes: d.Attributes.Clone(),
|
||||||
runtime: luaruntime.New(),
|
//runtime: luaruntime.New(),
|
||||||
Config: d.Config,
|
Config: d.Config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,6 +89,19 @@ func (d *Declaration) LoadDecl(yamlResourceDeclaration string) error {
|
|||||||
return codec.NewYAMLStringDecoder(yamlResourceDeclaration).Decode(d)
|
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 {
|
func (d *Declaration) NewResource() error {
|
||||||
uri := fmt.Sprintf("%s://", d.Type)
|
uri := fmt.Sprintf("%s://", d.Type)
|
||||||
newResource, err := ResourceTypes.New(uri)
|
newResource, err := ResourceTypes.New(uri)
|
||||||
|
@ -58,12 +58,12 @@ func TestNewResourceDeclarationType(t *testing.T) {
|
|||||||
`, file)
|
`, file)
|
||||||
|
|
||||||
resourceDeclaration := NewDeclaration()
|
resourceDeclaration := NewDeclaration()
|
||||||
assert.NotEqual(t, nil, resourceDeclaration)
|
assert.NotNil(t, resourceDeclaration)
|
||||||
|
|
||||||
e := resourceDeclaration.LoadDecl(decl)
|
e := resourceDeclaration.LoadDecl(decl)
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
assert.Equal(t, TypeName("file"), resourceDeclaration.Type)
|
assert.Equal(t, TypeName("file"), resourceDeclaration.Type)
|
||||||
assert.NotEqual(t, nil, resourceDeclaration.Attributes)
|
assert.NotNil(t, resourceDeclaration.Attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeclarationNewResource(t *testing.T) {
|
func TestDeclarationNewResource(t *testing.T) {
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
"encoding/json"
|
||||||
_ "log"
|
_ "log"
|
||||||
"net/url"
|
"net/url"
|
||||||
_ "os"
|
_ "os"
|
||||||
@ -15,19 +16,18 @@ _ "strings"
|
|||||||
"io"
|
"io"
|
||||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
|
"decl/internal/command"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Exec struct {
|
type Exec struct {
|
||||||
stater machine.Stater `yaml:"-" json:"-"`
|
stater machine.Stater `yaml:"-" json:"-"`
|
||||||
Id string `yaml:"id" json:"id"`
|
Id string `yaml:"id,omitempty" json:"id,omitempty"`
|
||||||
CreateTemplate Command `yaml:"create" json:"create"`
|
CreateTemplate *command.Command `yaml:"create,omitempty" json:"create,omitempty"`
|
||||||
ReadTemplate Command `yaml:"read" json:"read"`
|
ReadTemplate *command.Command `yaml:"read,omitempty" json:"read,omitempty"`
|
||||||
UpdateTemplate Command `yaml:"update" json:"update"`
|
UpdateTemplate *command.Command `yaml:"update,omitempty" json:"update,omitempty"`
|
||||||
DeleteTemplate Command `yaml:"delete" json:"delete"`
|
DeleteTemplate *command.Command `yaml:"delete,omitempty" json:"delete,omitempty"`
|
||||||
|
|
||||||
config ConfigurationValueGetter
|
config ConfigurationValueGetter
|
||||||
// state attributes
|
|
||||||
State string `yaml:"state"`
|
|
||||||
Resources ResourceMapper `yaml:"-" json:"-"`
|
Resources ResourceMapper `yaml:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,14 +53,12 @@ func (x *Exec) Clone() Resource {
|
|||||||
ReadTemplate: x.ReadTemplate,
|
ReadTemplate: x.ReadTemplate,
|
||||||
UpdateTemplate: x.UpdateTemplate,
|
UpdateTemplate: x.UpdateTemplate,
|
||||||
DeleteTemplate: x.DeleteTemplate,
|
DeleteTemplate: x.DeleteTemplate,
|
||||||
State: x.State,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) StateMachine() machine.Stater {
|
func (x *Exec) StateMachine() machine.Stater {
|
||||||
if x.stater == nil {
|
if x.stater == nil {
|
||||||
x.stater = ProcessMachine(x)
|
x.stater = ProcessMachine(x)
|
||||||
|
|
||||||
}
|
}
|
||||||
return x.stater
|
return x.stater
|
||||||
}
|
}
|
||||||
@ -89,8 +87,13 @@ func (x *Exec) ResolveId(ctx context.Context) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) Validate() error {
|
func (x *Exec) Validate() (err error) {
|
||||||
return fmt.Errorf("failed")
|
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 {
|
func (x *Exec) Apply() error {
|
||||||
@ -108,9 +111,7 @@ func (x *Exec) Notify(m *machine.EventMessage) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x.State = "absent"
|
|
||||||
case "present":
|
case "present":
|
||||||
x.State = "present"
|
|
||||||
}
|
}
|
||||||
case machine.EXITSTATEEVENT:
|
case machine.EXITSTATEEVENT:
|
||||||
}
|
}
|
||||||
@ -124,12 +125,21 @@ func (x *Exec) LoadDecl(yamlResourceDeclaration string) error {
|
|||||||
return codec.NewYAMLStringDecoder(yamlResourceDeclaration).Decode(x)
|
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) Type() string { return "exec" }
|
||||||
|
|
||||||
func (x *Exec) Create(ctx context.Context) error {
|
func (x *Exec) Create(ctx context.Context) (err error) {
|
||||||
return nil
|
x.CreateTemplate.Defaults()
|
||||||
|
if _, err = x.CreateTemplate.Execute(x); err == nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) Read(ctx context.Context) ([]byte, error) {
|
func (x *Exec) Read(ctx context.Context) ([]byte, error) {
|
||||||
|
x.ReadTemplate.Defaults()
|
||||||
return yaml.Marshal(x)
|
return yaml.Marshal(x)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved
|
||||||
|
|
||||||
|
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -15,6 +17,7 @@ import (
|
|||||||
_ "os"
|
_ "os"
|
||||||
_ "strings"
|
_ "strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"decl/internal/command"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewExecResource(t *testing.T) {
|
func TestNewExecResource(t *testing.T) {
|
||||||
@ -38,6 +41,17 @@ func TestReadExecError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateExec(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) {
|
func TestExecSetURI(t *testing.T) {
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -38,7 +39,6 @@ const (
|
|||||||
SocketFile FileType = "socket"
|
SocketFile FileType = "socket"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrInvalidResourceURI error = errors.New("Invalid resource URI")
|
|
||||||
var ErrInvalidFileInfo error = errors.New("Invalid FileInfo")
|
var ErrInvalidFileInfo error = errors.New("Invalid FileInfo")
|
||||||
var ErrInvalidFileMode error = errors.New("Invalid Mode")
|
var ErrInvalidFileMode error = errors.New("Invalid Mode")
|
||||||
var ErrInvalidFileOwner error = errors.New("Unknown User")
|
var ErrInvalidFileOwner error = errors.New("Unknown User")
|
||||||
@ -149,8 +149,10 @@ func (f *File) Notify(m *machine.EventMessage) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
f.State = "absent"
|
f.State = "absent"
|
||||||
|
if ! errors.Is(readErr, ErrResourceStateAbsent) {
|
||||||
panic(readErr)
|
panic(readErr)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case "start_create":
|
case "start_create":
|
||||||
if e := f.Create(ctx); e == nil {
|
if e := f.Create(ctx); e == nil {
|
||||||
if triggerErr := f.StateMachine().Trigger("created"); triggerErr == nil {
|
if triggerErr := f.StateMachine().Trigger("created"); triggerErr == nil {
|
||||||
@ -187,7 +189,7 @@ func (f *File) SetURI(uri string) error {
|
|||||||
resourceUri, e := url.Parse(uri)
|
resourceUri, e := url.Parse(uri)
|
||||||
if e == nil {
|
if e == nil {
|
||||||
if resourceUri.Scheme == "file" {
|
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 {
|
if err := f.NormalizePath(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -202,8 +204,17 @@ func (f *File) UseConfig(config ConfigurationValueGetter) {
|
|||||||
f.config = config
|
f.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Validate() error {
|
func (f *File) JSON() ([]byte, error) {
|
||||||
return fmt.Errorf("failed")
|
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 {
|
func (f *File) Apply() error {
|
||||||
@ -427,7 +438,7 @@ func (f *File) Read(ctx context.Context) ([]byte, error) {
|
|||||||
|
|
||||||
statErr := f.ReadStat()
|
statErr := f.ReadStat()
|
||||||
if statErr != nil {
|
if statErr != nil {
|
||||||
return nil, statErr
|
return nil, fmt.Errorf("%w - %w", ErrResourceStateAbsent, statErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch f.FileType {
|
switch f.FileType {
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"io/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewFileResource(t *testing.T) {
|
func TestNewFileResource(t *testing.T) {
|
||||||
@ -108,7 +109,7 @@ func TestReadFileError(t *testing.T) {
|
|||||||
assert.NotEqual(t, nil, f)
|
assert.NotEqual(t, nil, f)
|
||||||
f.Path = file
|
f.Path = file
|
||||||
_, e := f.Read(ctx)
|
_, e := f.Read(ctx)
|
||||||
assert.True(t, os.IsNotExist(e))
|
assert.ErrorIs(t, e, fs.ErrNotExist)
|
||||||
assert.Equal(t, "absent", f.State)
|
assert.Equal(t, "absent", f.State)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,3 +449,47 @@ func TestFileContentRef(t *testing.T) {
|
|||||||
assert.Nil(t, stater.Trigger("delete"))
|
assert.Nil(t, stater.Trigger("delete"))
|
||||||
assert.NoFileExists(t, file, nil)
|
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,8 +115,10 @@ func (p *Package) Notify(m *machine.EventMessage) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.State = "absent"
|
p.State = "absent"
|
||||||
|
if ! errors.Is(readErr, ErrResourceStateAbsent) {
|
||||||
panic(readErr)
|
panic(readErr)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case "start_create":
|
case "start_create":
|
||||||
if e := p.Create(ctx); e == nil {
|
if e := p.Create(ctx); e == nil {
|
||||||
if triggerErr := p.StateMachine().Trigger("created"); triggerErr == nil {
|
if triggerErr := p.StateMachine().Trigger("created"); triggerErr == nil {
|
||||||
@ -124,6 +126,12 @@ func (p *Package) Notify(m *machine.EventMessage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.State = "absent"
|
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":
|
case "start_delete":
|
||||||
if deleteErr := p.Delete(ctx); deleteErr == nil {
|
if deleteErr := p.Delete(ctx); deleteErr == nil {
|
||||||
if triggerErr := p.StateMachine().Trigger("deleted"); triggerErr == nil {
|
if triggerErr := p.StateMachine().Trigger("deleted"); triggerErr == nil {
|
||||||
@ -138,7 +146,7 @@ func (p *Package) Notify(m *machine.EventMessage) {
|
|||||||
}
|
}
|
||||||
case "absent":
|
case "absent":
|
||||||
p.State = "absent"
|
p.State = "absent"
|
||||||
case "present", "created", "read":
|
case "present", "created", "updated", "read":
|
||||||
p.State = "present"
|
p.State = "present"
|
||||||
}
|
}
|
||||||
case machine.EXITSTATEEVENT:
|
case machine.EXITSTATEEVENT:
|
||||||
@ -187,7 +195,46 @@ func (p *Package) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Package) ResolveId(ctx context.Context) string {
|
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 {
|
func (p *Package) Create(ctx context.Context) error {
|
||||||
@ -202,6 +249,10 @@ func (p *Package) Create(ctx context.Context) error {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Package) Update(ctx context.Context) error {
|
||||||
|
return p.Create(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Package) Delete(ctx context.Context) error {
|
func (p *Package) Delete(ctx context.Context) error {
|
||||||
_, err := p.DeleteCommand.Execute(p)
|
_, err := p.DeleteCommand.Execute(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -235,20 +286,24 @@ func (p *Package) LoadDecl(yamlResourceDeclaration string) error {
|
|||||||
|
|
||||||
func (p *Package) Type() string { return "package" }
|
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() {
|
if p.ReadCommand.Exists() {
|
||||||
out, err := p.ReadCommand.Execute(p)
|
var out []byte
|
||||||
if err != nil {
|
out, err = p.ReadCommand.Execute(p)
|
||||||
return nil, err
|
if err == nil {
|
||||||
}
|
err = p.ReadCommand.Extractor(out, p)
|
||||||
exErr := p.ReadCommand.Extractor(out, p)
|
|
||||||
if exErr != nil {
|
|
||||||
return nil, exErr
|
|
||||||
}
|
|
||||||
return yaml.Marshal(p)
|
|
||||||
} else {
|
} else {
|
||||||
return nil, ErrUnsupportedPackageType
|
err = fmt.Errorf("%w - %w", ErrResourceStateAbsent, err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
err = ErrUnsupportedPackageType
|
||||||
|
}
|
||||||
|
var yamlErr error
|
||||||
|
resourceYaml, yamlErr = yaml.Marshal(p)
|
||||||
|
if err == nil {
|
||||||
|
err = yamlErr
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Package) UnmarshalJSON(data []byte) error {
|
func (p *Package) UnmarshalJSON(data []byte) error {
|
||||||
|
@ -5,7 +5,7 @@ package resource
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "encoding/json"
|
_ "encoding/json"
|
||||||
_ "fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
_ "gopkg.in/yaml.v3"
|
_ "gopkg.in/yaml.v3"
|
||||||
_ "io"
|
_ "io"
|
||||||
@ -95,6 +95,34 @@ Version: 1.2.2
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadPackageError(t *testing.T) {
|
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) {
|
func TestCreatePackage(t *testing.T) {
|
||||||
|
@ -161,6 +161,9 @@ func (k *PKI) Notify(m *machine.EventMessage) {
|
|||||||
|
|
||||||
func (k *PKI) URI() string {
|
func (k *PKI) URI() string {
|
||||||
u := k.PrivateKeyRef.Parse()
|
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()))
|
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)
|
resourceUri, e := url.Parse(uri)
|
||||||
if e == nil {
|
if e == nil {
|
||||||
if resourceUri.Scheme == "pki" {
|
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 {
|
} else {
|
||||||
e = fmt.Errorf("%w: %s is not a cert", ErrInvalidResourceURI, uri)
|
e = fmt.Errorf("%w: %s is not a cert", ErrInvalidResourceURI, uri)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,13 @@ import (
|
|||||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||||
"decl/internal/transport"
|
"decl/internal/transport"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidResourceURI error = errors.New("Invalid resource URI")
|
||||||
|
ErrResourceStateAbsent = errors.New("Resource state absent")
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResourceReference string
|
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",
|
"$id": "container-declaration.schema.json",
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "declaration",
|
"title": "container-declaration",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [ "type", "attributes" ],
|
"required": [ "type", "attributes" ],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$id": "container-image-declaration.schema.json",
|
"$id": "container-image-declaration.schema.json",
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "declaration",
|
"title": "container-image-declaration",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [ "type", "attributes" ],
|
"required": [ "type", "attributes" ],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -9,6 +9,12 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"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})|)$"
|
"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",
|
"$id": "container-network-declaration.schema.json",
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "declaration",
|
"title": "container-network-declaration",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [ "type", "attributes" ],
|
"required": [ "type", "attributes" ],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -3,19 +3,22 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "exec",
|
"title": "exec",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [ "create", "read" ],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"Id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"type": "string"
|
"$ref": "command.schema.json"
|
||||||
},
|
},
|
||||||
"read": {
|
"Read": {
|
||||||
"type": "string"
|
"$ref": "command.schema.json"
|
||||||
},
|
},
|
||||||
"update": {
|
"Update": {
|
||||||
"type": "string"
|
"$ref": "command.schema.json"
|
||||||
},
|
},
|
||||||
"delete": {
|
"Delete": {
|
||||||
"type": "string"
|
"$ref": "command.schema.json"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"required": [ "create" ]
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$id": "group.schema.json",
|
"$id": "group.schema.json",
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "group",
|
"title": "group-declaration",
|
||||||
"description": "A group account",
|
"description": "A group account",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [ "name" ],
|
"required": [ "name" ],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$id": "pki-declaration.schema.json",
|
"$id": "pki-declaration.schema.json",
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "declaration",
|
"title": "pki-declaration",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [ "type", "attributes" ],
|
"required": [ "type", "attributes" ],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -6,5 +6,4 @@
|
|||||||
"description": "Storage state transition",
|
"description": "Storage state transition",
|
||||||
"enum": [ "absent", "present", "create", "read", "update", "delete" ]
|
"enum": [ "absent", "present", "create", "read", "update", "delete" ]
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$id": "user-declaration.schema.json",
|
"$id": "user-declaration.schema.json",
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "declaration",
|
"title": "user-declaration",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [ "type", "attributes" ],
|
"required": [ "type", "attributes" ],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -32,7 +32,7 @@ func NewDeclFile() *DeclFile {
|
|||||||
func init() {
|
func init() {
|
||||||
SourceTypes.Register([]string{"decl"}, func(u *url.URL) DocSource {
|
SourceTypes.Register([]string{"decl"}, func(u *url.URL) DocSource {
|
||||||
t := NewDeclFile()
|
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)
|
t.transport,_ = transport.NewReader(u)
|
||||||
return t
|
return t
|
||||||
})
|
})
|
||||||
@ -40,7 +40,7 @@ func init() {
|
|||||||
SourceTypes.Register([]string{"yaml","yml","yaml.gz","yml.gz"}, func(u *url.URL) DocSource {
|
SourceTypes.Register([]string{"yaml","yml","yaml.gz","yml.gz"}, func(u *url.URL) DocSource {
|
||||||
t := NewDeclFile()
|
t := NewDeclFile()
|
||||||
if u.Scheme == "file" {
|
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
|
t.Path = fileAbsolutePath
|
||||||
} else {
|
} else {
|
||||||
t.Path = filepath.Join(u.Hostname(), u.Path)
|
t.Path = filepath.Join(u.Hostname(), u.Path)
|
||||||
|
@ -28,7 +28,7 @@ func NewDir() *Dir {
|
|||||||
func init() {
|
func init() {
|
||||||
SourceTypes.Register([]string{"file"}, func(u *url.URL) DocSource {
|
SourceTypes.Register([]string{"file"}, func(u *url.URL) DocSource {
|
||||||
t := NewDir()
|
t := NewDir()
|
||||||
t.Path,_ = filepath.Abs(filepath.Join(u.Hostname(), u.RequestURI()))
|
t.Path,_ = filepath.Abs(filepath.Join(u.Hostname(), u.Path))
|
||||||
return t
|
return t
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ func init() {
|
|||||||
SourceTypes.Register([]string{"tar.gz", "tgz"}, func(u *url.URL) DocSource {
|
SourceTypes.Register([]string{"tar.gz", "tgz"}, func(u *url.URL) DocSource {
|
||||||
t := NewTar()
|
t := NewTar()
|
||||||
if u.Scheme == "file" {
|
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
|
t.Path = fileAbsolutePath
|
||||||
} else {
|
} else {
|
||||||
t.Path = filepath.Join(u.Hostname(), u.Path)
|
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.Format = format
|
||||||
t.Gzip = gzip
|
t.Gzip = gzip
|
||||||
if fileUri {
|
if fileUri {
|
||||||
fileAbsolutePath, _ := filepath.Abs(filepath.Join(u.Hostname(), u.RequestURI()))
|
fileAbsolutePath, _ := filepath.Abs(filepath.Join(u.Hostname(), u.Path))
|
||||||
t.Path = fileAbsolutePath
|
t.Path = fileAbsolutePath
|
||||||
} else {
|
} else {
|
||||||
t.Path = filepath.Join(u.Hostname(), u.Path)
|
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 {
|
TargetTypes.Register([]string{"tar.gz", "tgz"}, func(u *url.URL) DocTarget {
|
||||||
t := NewTar()
|
t := NewTar()
|
||||||
if u.Scheme == "file" {
|
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
|
t.Path = fileAbsolutePath
|
||||||
} else {
|
} else {
|
||||||
t.Path = filepath.Join(u.Hostname(), u.Path)
|
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 {
|
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 {
|
func FileExists(u *url.URL) bool {
|
||||||
|
@ -32,7 +32,7 @@ func TestNewTransportFileReaderExtension(t *testing.T) {
|
|||||||
|
|
||||||
f := &File{
|
f := &File{
|
||||||
uri: u,
|
uri: u,
|
||||||
path: filepath.Join(u.Hostname(), u.RequestURI()),
|
path: filepath.Join(u.Hostname(), u.Path),
|
||||||
}
|
}
|
||||||
f.extension()
|
f.extension()
|
||||||
assert.Equal(t, f.exttype, "yaml")
|
assert.Equal(t, f.exttype, "yaml")
|
||||||
|
@ -86,10 +86,11 @@ func NewWriterURI(uri string) (writer *Writer, e error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExistsURI(uri string) bool {
|
func ExistsURI(uri string) bool {
|
||||||
var u *url.URL
|
if u, e := url.Parse(uri); e == nil {
|
||||||
u, _ = url.Parse(uri)
|
|
||||||
return Exists(u)
|
return Exists(u)
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func Exists(u *url.URL) bool {
|
func Exists(u *url.URL) bool {
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
|
@ -65,6 +65,16 @@ func TestTransportReaderContentType(t *testing.T) {
|
|||||||
assert.Equal(t, reader.ContentType(), "yaml")
|
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) {
|
func TestTransportWriter(t *testing.T) {
|
||||||
path := fmt.Sprintf("%s/writefoo", TempDir)
|
path := fmt.Sprintf("%s/writefoo", TempDir)
|
||||||
u, e := url.Parse(fmt.Sprintf("file://%s", path))
|
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 (
|
var (
|
||||||
ErrUnknownType = errors.New("Unknown type")
|
ErrUnknownType = errors.New("Unknown type")
|
||||||
|
ErrInvalidProduct = errors.New("Invalid product")
|
||||||
)
|
)
|
||||||
|
|
||||||
//type Name[Registry any] string //`json:"type"`
|
//type Name[Registry any] string //`json:"type"`
|
||||||
|
|
||||||
type Factory[Product any] func(*url.URL) Product
|
type Factory[Product comparable] func(*url.URL) Product
|
||||||
type RegistryTypeMap[Product any] map[string]Factory[Product]
|
type RegistryTypeMap[Product comparable] map[string]Factory[Product]
|
||||||
|
|
||||||
type Types[Product any] struct {
|
type Types[Product comparable] struct {
|
||||||
registry RegistryTypeMap[Product]
|
registry RegistryTypeMap[Product]
|
||||||
}
|
}
|
||||||
|
|
||||||
func New[Product any]() *Types[Product] {
|
func New[Product comparable]() *Types[Product] {
|
||||||
return &Types[Product]{registry: make(map[string]Factory[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 {
|
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)
|
err = fmt.Errorf("%w: %s", ErrUnknownType, u.Scheme)
|
||||||
return
|
return
|
||||||
|
@ -26,6 +26,7 @@ type MockContainerClient struct {
|
|||||||
InjectImagePull func(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error)
|
InjectImagePull func(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error)
|
||||||
InjectImageInspectWithRaw func(ctx context.Context, imageID string) (types.ImageInspect, []byte, 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)
|
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
|
InjectClose func() error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +42,10 @@ func (m *MockContainerClient) ImagePull(ctx context.Context, refStr string, opti
|
|||||||
return m.InjectImagePull(ctx, refStr, options)
|
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) {
|
func (m *MockContainerClient) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) {
|
||||||
return m.InjectImageInspectWithRaw(ctx, imageID)
|
return m.InjectImageInspectWithRaw(ctx, imageID)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user