This commit is contained in:
parent
e71d177984
commit
e695278d0c
@ -1,260 +1,257 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Container resource
|
// Container resource
|
||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "os"
|
"github.com/docker/docker/api/types"
|
||||||
_ "gopkg.in/yaml.v3"
|
"github.com/docker/docker/api/types/container"
|
||||||
_ "os/exec"
|
"github.com/docker/docker/api/types/filters"
|
||||||
_ "strings"
|
"github.com/docker/docker/api/types/mount"
|
||||||
"log/slog"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/api/types/strslice"
|
"github.com/docker/docker/api/types/strslice"
|
||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/api/types/container"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"gopkg.in/yaml.v3"
|
||||||
"github.com/docker/docker/api/types"
|
_ "gopkg.in/yaml.v3"
|
||||||
"github.com/docker/docker/client"
|
"log/slog"
|
||||||
"github.com/docker/docker/api/types/network"
|
"net/url"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
_ "os"
|
||||||
"gopkg.in/yaml.v3"
|
_ "os/exec"
|
||||||
"net/url"
|
"path/filepath"
|
||||||
"path/filepath"
|
_ "strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContainerClient interface {
|
type ContainerClient interface {
|
||||||
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
|
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
|
||||||
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error
|
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error
|
||||||
ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error)
|
ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error)
|
||||||
ContainerInspect(context.Context, string) (types.ContainerJSON, error)
|
ContainerInspect(context.Context, string) (types.ContainerJSON, error)
|
||||||
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
loader YamlLoader
|
loader YamlLoader
|
||||||
Id string `yaml:"ID",omitempty`
|
Id string `yaml:"ID",omitempty`
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Path string `yaml:"path"`
|
Path string `yaml:"path"`
|
||||||
Cmd []string `yaml:"cmd",omitempty`
|
Cmd []string `yaml:"cmd",omitempty`
|
||||||
Entrypoint strslice.StrSlice `yaml:"entrypoint",omitempty`
|
Entrypoint strslice.StrSlice `yaml:"entrypoint",omitempty`
|
||||||
Args []string `yaml:"args",omitempty`
|
Args []string `yaml:"args",omitempty`
|
||||||
Environment map[string]string `yaml:"environment"`
|
Environment map[string]string `yaml:"environment"`
|
||||||
Image string `yaml:"image"`
|
Image string `yaml:"image"`
|
||||||
ResolvConfPath string `yaml:"resolvconfpath"`
|
ResolvConfPath string `yaml:"resolvconfpath"`
|
||||||
HostnamePath string `yaml:"hostnamepath"`
|
HostnamePath string `yaml:"hostnamepath"`
|
||||||
HostsPath string `yaml:"hostspath"`
|
HostsPath string `yaml:"hostspath"`
|
||||||
LogPath string `yaml:"logpath"`
|
LogPath string `yaml:"logpath"`
|
||||||
Created string `yaml:"created"`
|
Created string `yaml:"created"`
|
||||||
ContainerState types.ContainerState `yaml:"containerstate"`
|
ContainerState types.ContainerState `yaml:"containerstate"`
|
||||||
RestartCount int `yaml:"restartcount"`
|
RestartCount int `yaml:"restartcount"`
|
||||||
Driver string `yaml:"driver"`
|
Driver string `yaml:"driver"`
|
||||||
Platform string `yaml:"platform"`
|
Platform string `yaml:"platform"`
|
||||||
MountLabel string `yaml:"mountlabel"`
|
MountLabel string `yaml:"mountlabel"`
|
||||||
ProcessLabel string `yaml:"processlabel"`
|
ProcessLabel string `yaml:"processlabel"`
|
||||||
AppArmorProfile string `yaml:"apparmorprofile"`
|
AppArmorProfile string `yaml:"apparmorprofile"`
|
||||||
ExecIDs []string `yaml:"execids"`
|
ExecIDs []string `yaml:"execids"`
|
||||||
HostConfig container.HostConfig `yaml:"hostconfig"`
|
HostConfig container.HostConfig `yaml:"hostconfig"`
|
||||||
GraphDriver types.GraphDriverData `yaml:"graphdriver"`
|
GraphDriver types.GraphDriverData `yaml:"graphdriver"`
|
||||||
SizeRw *int64 `json:",omitempty"`
|
SizeRw *int64 `json:",omitempty"`
|
||||||
SizeRootFs *int64 `json:",omitempty"`
|
SizeRootFs *int64 `json:",omitempty"`
|
||||||
/*
|
/*
|
||||||
Mounts []MountPoint
|
Mounts []MountPoint
|
||||||
Config *container.Config
|
Config *container.Config
|
||||||
NetworkSettings *NetworkSettings
|
NetworkSettings *NetworkSettings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
State string `yaml:"state"`
|
State string `yaml:"state"`
|
||||||
|
|
||||||
apiClient ContainerClient
|
apiClient ContainerClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ResourceTypes.Register("container", func(u *url.URL) Resource {
|
ResourceTypes.Register("container", func(u *url.URL) Resource {
|
||||||
c := NewContainer(nil)
|
c := NewContainer(nil)
|
||||||
c.Name = filepath.Join(u.Hostname(), u.Path)
|
c.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
return c
|
return c
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContainer(containerClientApi ContainerClient) *Container {
|
func NewContainer(containerClientApi ContainerClient) *Container {
|
||||||
var apiClient ContainerClient = containerClientApi
|
var apiClient ContainerClient = containerClientApi
|
||||||
if apiClient == nil {
|
if apiClient == nil {
|
||||||
var err error
|
var err error
|
||||||
apiClient, err = client.NewClientWithOpts(client.FromEnv)
|
apiClient, err = client.NewClientWithOpts(client.FromEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &Container{
|
return &Container{
|
||||||
loader: YamlLoadDecl,
|
loader: YamlLoadDecl,
|
||||||
apiClient: apiClient,
|
apiClient: apiClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) URI() string {
|
func (c *Container) URI() string {
|
||||||
return fmt.Sprintf("container://%s", c.Id)
|
return fmt.Sprintf("container://%s", c.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) SetURI(uri string) error {
|
func (c *Container) SetURI(uri string) error {
|
||||||
resourceUri, e := url.Parse(uri)
|
resourceUri, e := url.Parse(uri)
|
||||||
if resourceUri.Scheme == c.Type() {
|
if resourceUri.Scheme == c.Type() {
|
||||||
c.Name, e = filepath.Abs(filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI()))
|
c.Name, e = filepath.Abs(filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI()))
|
||||||
} else {
|
} else {
|
||||||
e = fmt.Errorf("%w: %s is not a %s", ErrInvalidResourceURI, uri, c.Type())
|
e = fmt.Errorf("%w: %s is not a %s", ErrInvalidResourceURI, uri, c.Type())
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) Apply() error {
|
func (c *Container) Apply() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
switch c.State {
|
switch c.State {
|
||||||
case "absent":
|
case "absent":
|
||||||
return c.Delete(ctx)
|
return c.Delete(ctx)
|
||||||
case "present":
|
case "present":
|
||||||
return c.Create(ctx)
|
return c.Create(ctx)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) LoadDecl(yamlFileResourceDeclaration string) error {
|
func (c *Container) LoadDecl(yamlFileResourceDeclaration string) error {
|
||||||
return c.loader(yamlFileResourceDeclaration, c)
|
return c.loader(yamlFileResourceDeclaration, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) Create(ctx context.Context) error {
|
func (c *Container) Create(ctx context.Context) error {
|
||||||
numberOfEnvironmentVariables := len(c.Environment)
|
numberOfEnvironmentVariables := len(c.Environment)
|
||||||
config := &container.Config {
|
config := &container.Config{
|
||||||
Image: c.Image,
|
Image: c.Image,
|
||||||
Cmd: c.Cmd,
|
Cmd: c.Cmd,
|
||||||
Entrypoint: c.Entrypoint,
|
Entrypoint: c.Entrypoint,
|
||||||
Tty: false,
|
Tty: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Env = make([]string, numberOfEnvironmentVariables)
|
config.Env = make([]string, numberOfEnvironmentVariables)
|
||||||
index := 0
|
index := 0
|
||||||
for k,v := range c.Environment {
|
for k, v := range c.Environment {
|
||||||
config.Env[index] = k + "=" + v
|
config.Env[index] = k + "=" + v
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
for i := range c.HostConfig.Mounts {
|
for i := range c.HostConfig.Mounts {
|
||||||
if c.HostConfig.Mounts[i].Type == mount.TypeBind {
|
if c.HostConfig.Mounts[i].Type == mount.TypeBind {
|
||||||
if mountSourceAbsolutePath,e := filepath.Abs(c.HostConfig.Mounts[i].Source); e == nil {
|
if mountSourceAbsolutePath, e := filepath.Abs(c.HostConfig.Mounts[i].Source); e == nil {
|
||||||
c.HostConfig.Mounts[i].Source = mountSourceAbsolutePath
|
c.HostConfig.Mounts[i].Source = mountSourceAbsolutePath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.apiClient.ContainerCreate(ctx, config, &c.HostConfig, nil, nil, c.Name)
|
resp, err := c.apiClient.ContainerCreate(ctx, config, &c.HostConfig, nil, nil, c.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
c.Id = resp.ID
|
c.Id = resp.ID
|
||||||
|
|
||||||
/*
|
/*
|
||||||
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
|
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
|
||||||
select {
|
select {
|
||||||
case err := <-errCh:
|
case err := <-errCh:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
case <-statusCh:
|
case <-statusCh:
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if startErr := c.apiClient.ContainerStart(ctx, c.Id, types.ContainerStartOptions{}); startErr != nil {
|
if startErr := c.apiClient.ContainerStart(ctx, c.Id, types.ContainerStartOptions{}); startErr != nil {
|
||||||
return startErr
|
return startErr
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// produce yaml representation of any resource
|
// produce yaml representation of any resource
|
||||||
|
|
||||||
func (c *Container) Read(ctx context.Context) ([]byte, error) {
|
func (c *Container) Read(ctx context.Context) ([]byte, error) {
|
||||||
var containerID string
|
var containerID string
|
||||||
filterArgs := filters.NewArgs()
|
filterArgs := filters.NewArgs()
|
||||||
filterArgs.Add("name", "/" + c.Name)
|
filterArgs.Add("name", "/"+c.Name)
|
||||||
containers,err := c.apiClient.ContainerList(ctx, types.ContainerListOptions{
|
containers, err := c.apiClient.ContainerList(ctx, types.ContainerListOptions{
|
||||||
All: true,
|
All: true,
|
||||||
Filters: filterArgs,
|
Filters: filterArgs,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("%w: %s %s", err, c.Type(), c.Name))
|
panic(fmt.Errorf("%w: %s %s", err, c.Type(), c.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
for _, containerName := range container.Names {
|
for _, containerName := range container.Names {
|
||||||
if containerName == "/" + c.Name {
|
if containerName == "/"+c.Name {
|
||||||
containerID = container.ID
|
containerID = container.ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
containerJSON, err := c.apiClient.ContainerInspect(ctx, containerID)
|
containerJSON, err := c.apiClient.ContainerInspect(ctx, containerID)
|
||||||
if client.IsErrNotFound(err) {
|
if client.IsErrNotFound(err) {
|
||||||
c.State = "absent"
|
c.State = "absent"
|
||||||
} else {
|
} else {
|
||||||
c.State = "present"
|
c.State = "present"
|
||||||
c.Id = containerJSON.ID
|
c.Id = containerJSON.ID
|
||||||
if c.Name == "" {
|
if c.Name == "" {
|
||||||
c.Name = containerJSON.Name
|
c.Name = containerJSON.Name
|
||||||
}
|
}
|
||||||
c.Path = containerJSON.Path
|
c.Path = containerJSON.Path
|
||||||
c.Image = containerJSON.Image
|
c.Image = containerJSON.Image
|
||||||
if containerJSON.State != nil {
|
if containerJSON.State != nil {
|
||||||
c.ContainerState = *containerJSON.State
|
c.ContainerState = *containerJSON.State
|
||||||
}
|
}
|
||||||
c.Created = containerJSON.Created
|
c.Created = containerJSON.Created
|
||||||
c.ResolvConfPath = containerJSON.ResolvConfPath
|
c.ResolvConfPath = containerJSON.ResolvConfPath
|
||||||
c.HostnamePath = containerJSON.HostnamePath
|
c.HostnamePath = containerJSON.HostnamePath
|
||||||
c.HostsPath = containerJSON.HostsPath
|
c.HostsPath = containerJSON.HostsPath
|
||||||
c.LogPath = containerJSON.LogPath
|
c.LogPath = containerJSON.LogPath
|
||||||
c.RestartCount = containerJSON.RestartCount
|
c.RestartCount = containerJSON.RestartCount
|
||||||
c.Driver = containerJSON.Driver
|
c.Driver = containerJSON.Driver
|
||||||
}
|
}
|
||||||
slog.Info("Read() ", "type", c.Type(), "name", c.Name, "Id", c.Id)
|
slog.Info("Read() ", "type", c.Type(), "name", c.Name, "Id", c.Id)
|
||||||
return yaml.Marshal(c)
|
return yaml.Marshal(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (c *Container) Delete(ctx context.Context) error {
|
func (c *Container) Delete(ctx context.Context) error {
|
||||||
err := c.apiClient.ContainerRemove(ctx, c.Id, types.ContainerRemoveOptions{
|
err := c.apiClient.ContainerRemove(ctx, c.Id, types.ContainerRemoveOptions{
|
||||||
RemoveVolumes: true,
|
RemoveVolumes: true,
|
||||||
Force: false,
|
Force: false,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to remove: ", "Id", c.Id)
|
slog.Error("Failed to remove: ", "Id", c.Id)
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) Type() string { return "container" }
|
func (c *Container) Type() string { return "container" }
|
||||||
|
|
||||||
func (c *Container) ResolveId(ctx context.Context) string {
|
func (c *Container) ResolveId(ctx context.Context) string {
|
||||||
filterArgs := filters.NewArgs()
|
filterArgs := filters.NewArgs()
|
||||||
filterArgs.Add("name", "/" + c.Name)
|
filterArgs.Add("name", "/"+c.Name)
|
||||||
containers,err := c.apiClient.ContainerList(ctx, types.ContainerListOptions{
|
containers, err := c.apiClient.ContainerList(ctx, types.ContainerListOptions{
|
||||||
All: true,
|
All: true,
|
||||||
Filters: filterArgs,
|
Filters: filterArgs,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("%w: %s %s", err, c.Type(), c.Name))
|
panic(fmt.Errorf("%w: %s %s", err, c.Type(), c.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
for _, containerName := range container.Names {
|
for _, containerName := range container.Names {
|
||||||
if containerName == c.Name {
|
if containerName == c.Name {
|
||||||
if c.Id == "" {
|
if c.Id == "" {
|
||||||
c.Id = container.ID
|
c.Id = container.ID
|
||||||
}
|
}
|
||||||
return container.ID
|
return container.ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -1,93 +1,92 @@
|
|||||||
// 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 (
|
||||||
_ "fmt"
|
"context"
|
||||||
"context"
|
"decl/tests/mocks"
|
||||||
"testing"
|
_ "encoding/json"
|
||||||
_ "net/http"
|
_ "fmt"
|
||||||
_ "net/http/httptest"
|
"github.com/docker/docker/api/types"
|
||||||
_ "net/url"
|
"github.com/docker/docker/api/types/container"
|
||||||
_ "io"
|
"github.com/docker/docker/api/types/network"
|
||||||
_ "os"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
_ "encoding/json"
|
_ "io"
|
||||||
_ "strings"
|
_ "net/http"
|
||||||
"decl/tests/mocks"
|
_ "net/http/httptest"
|
||||||
"github.com/docker/docker/api/types"
|
_ "net/url"
|
||||||
"github.com/docker/docker/api/types/container"
|
_ "os"
|
||||||
"github.com/docker/docker/api/types/network"
|
_ "strings"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewContainerResource(t *testing.T) {
|
func TestNewContainerResource(t *testing.T) {
|
||||||
c := NewContainer(&mocks.MockContainerClient{})
|
c := NewContainer(&mocks.MockContainerClient{})
|
||||||
assert.NotEqual(t, nil, c)
|
assert.NotEqual(t, nil, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadContainer(t *testing.T) {
|
func TestReadContainer(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
decl := `
|
decl := `
|
||||||
name: "testcontainer"
|
name: "testcontainer"
|
||||||
image: "alpine"
|
image: "alpine"
|
||||||
state: present
|
state: present
|
||||||
`
|
`
|
||||||
m := &mocks.MockContainerClient {
|
m := &mocks.MockContainerClient{
|
||||||
InjectContainerList: func(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
|
InjectContainerList: func(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
|
||||||
return []types.Container{
|
return []types.Container{
|
||||||
{ ID: "123456789abc" },
|
{ID: "123456789abc"},
|
||||||
{ ID: "123456789def" },
|
{ID: "123456789def"},
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
InjectContainerInspect: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
InjectContainerInspect: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
||||||
return types.ContainerJSON{
|
return types.ContainerJSON{
|
||||||
ContainerJSONBase: &types.ContainerJSONBase{
|
ContainerJSONBase: &types.ContainerJSONBase{
|
||||||
ID: "123456789abc",
|
ID: "123456789abc",
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Image: "alpine",
|
Image: "alpine",
|
||||||
} }, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
c := NewContainer(m)
|
c := NewContainer(m)
|
||||||
assert.NotEqual(t, nil, c)
|
assert.NotEqual(t, nil, c)
|
||||||
|
|
||||||
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, "testcontainer", c.Name)
|
||||||
|
|
||||||
resourceYaml, readContainerErr := c.Read(ctx)
|
resourceYaml, readContainerErr := c.Read(ctx)
|
||||||
assert.Equal(t, nil, readContainerErr)
|
assert.Equal(t, nil, readContainerErr)
|
||||||
assert.Greater(t, len(resourceYaml), 0)
|
assert.Greater(t, len(resourceYaml), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateContainer(t *testing.T) {
|
func TestCreateContainer(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) {
|
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
|
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
||||||
},
|
},
|
||||||
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
|
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
decl := `
|
decl := `
|
||||||
name: "testcontainer"
|
name: "testcontainer"
|
||||||
image: "alpine"
|
image: "alpine"
|
||||||
state: present
|
state: present
|
||||||
`
|
`
|
||||||
c := NewContainer(m)
|
c := NewContainer(m)
|
||||||
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, "testcontainer", c.Name)
|
||||||
|
|
||||||
applyErr := c.Apply()
|
applyErr := c.Apply()
|
||||||
assert.Equal(t, nil, applyErr)
|
assert.Equal(t, nil, applyErr)
|
||||||
|
|
||||||
c.State = "absent"
|
c.State = "absent"
|
||||||
|
|
||||||
applyDeleteErr := c.Apply()
|
applyDeleteErr := c.Apply()
|
||||||
assert.Equal(t, nil, applyDeleteErr)
|
assert.Equal(t, nil, applyDeleteErr)
|
||||||
}
|
}
|
||||||
|
@ -1,84 +1,83 @@
|
|||||||
// 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 (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"gopkg.in/yaml.v3"
|
||||||
"gopkg.in/yaml.v3"
|
"log/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Declaration struct {
|
type Declaration struct {
|
||||||
Type string `yaml:"type"`
|
Type string `yaml:"type"`
|
||||||
Attributes yaml.Node `yaml:"attributes"`
|
Attributes yaml.Node `yaml:"attributes"`
|
||||||
Implementation Resource `-`
|
Implementation Resource `-`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceLoader interface {
|
type ResourceLoader interface {
|
||||||
LoadDecl(string) error
|
LoadDecl(string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type StateTransformer interface {
|
type StateTransformer interface {
|
||||||
Apply() error
|
Apply() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type YamlLoader func(string, any) error
|
type YamlLoader func(string, any) error
|
||||||
|
|
||||||
func YamlLoadDecl(yamlFileResourceDeclaration string, resource any) error {
|
func YamlLoadDecl(yamlFileResourceDeclaration string, resource any) error {
|
||||||
if err := yaml.Unmarshal([]byte(yamlFileResourceDeclaration), resource); err != nil {
|
if err := yaml.Unmarshal([]byte(yamlFileResourceDeclaration), resource); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeclaration() *Declaration {
|
func NewDeclaration() *Declaration {
|
||||||
return &Declaration{}
|
return &Declaration{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) LoadDecl(yamlResourceDeclaration string) error {
|
func (d *Declaration) LoadDecl(yamlResourceDeclaration string) error {
|
||||||
return YamlLoadDecl(yamlResourceDeclaration, d)
|
return YamlLoadDecl(yamlResourceDeclaration, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
d.Implementation = newResource
|
d.Implementation = newResource
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) LoadResourceFromYaml() (Resource, error) {
|
func (d *Declaration) LoadResourceFromYaml() (Resource, error) {
|
||||||
var errResource error
|
var errResource error
|
||||||
if d.Implementation == nil {
|
if d.Implementation == nil {
|
||||||
errResource = d.NewResource()
|
errResource = d.NewResource()
|
||||||
if errResource != nil {
|
if errResource != nil {
|
||||||
return nil, errResource
|
return nil, errResource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.Attributes.Decode(d.Implementation)
|
d.Attributes.Decode(d.Implementation)
|
||||||
d.Implementation.ResolveId(context.Background())
|
d.Implementation.ResolveId(context.Background())
|
||||||
return d.Implementation, errResource
|
return d.Implementation, errResource
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) UpdateYamlFromResource() error {
|
func (d *Declaration) UpdateYamlFromResource() error {
|
||||||
if d.Implementation != nil {
|
if d.Implementation != nil {
|
||||||
return d.Attributes.Encode(d.Implementation)
|
return d.Attributes.Encode(d.Implementation)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) Resource() Resource {
|
func (d *Declaration) Resource() Resource {
|
||||||
return d.Implementation
|
return d.Implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) SetURI(uri string) error {
|
func (d *Declaration) SetURI(uri string) error {
|
||||||
slog.Info("SetURI()", "uri", uri)
|
slog.Info("SetURI()", "uri", uri)
|
||||||
d.Implementation = NewResource(uri)
|
d.Implementation = NewResource(uri)
|
||||||
if d.Implementation == nil {
|
if d.Implementation == nil {
|
||||||
panic("unknown resource")
|
panic("unknown resource")
|
||||||
}
|
}
|
||||||
d.Type = d.Implementation.Type()
|
d.Type = d.Implementation.Type()
|
||||||
d.Implementation.Read(context.Background()) // fix context
|
d.Implementation.Read(context.Background()) // fix context
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,20 @@
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "os"
|
"fmt"
|
||||||
"path/filepath"
|
"github.com/stretchr/testify/assert"
|
||||||
"fmt"
|
_ "log"
|
||||||
_ "log"
|
_ "os"
|
||||||
"testing"
|
"path/filepath"
|
||||||
"github.com/stretchr/testify/assert"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestYamlLoadDecl(t *testing.T) {
|
func TestYamlLoadDecl(t *testing.T) {
|
||||||
|
|
||||||
file := filepath.Join(TempDir, "fooread.txt")
|
file := filepath.Join(TempDir, "fooread.txt")
|
||||||
|
|
||||||
resourceAttributes := make(map[string]any)
|
resourceAttributes := make(map[string]any)
|
||||||
decl := fmt.Sprintf(`
|
decl := fmt.Sprintf(`
|
||||||
path: "%s"
|
path: "%s"
|
||||||
owner: "nobody"
|
owner: "nobody"
|
||||||
group: "nobody"
|
group: "nobody"
|
||||||
@ -25,21 +25,21 @@ func TestYamlLoadDecl(t *testing.T) {
|
|||||||
test line 2
|
test line 2
|
||||||
`, file)
|
`, file)
|
||||||
|
|
||||||
e := YamlLoadDecl(decl, &resourceAttributes)
|
e := YamlLoadDecl(decl, &resourceAttributes)
|
||||||
assert.Equal(t, nil, e)
|
assert.Equal(t, nil, e)
|
||||||
|
|
||||||
assert.Equal(t, "nobody", resourceAttributes["group"])
|
assert.Equal(t, "nobody", resourceAttributes["group"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewResourceDeclaration(t *testing.T) {
|
func TestNewResourceDeclaration(t *testing.T) {
|
||||||
resourceDeclaration := NewDeclaration()
|
resourceDeclaration := NewDeclaration()
|
||||||
assert.NotEqual(t, nil, resourceDeclaration)
|
assert.NotEqual(t, nil, resourceDeclaration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewResourceDeclarationType(t *testing.T) {
|
func TestNewResourceDeclarationType(t *testing.T) {
|
||||||
file := filepath.Join(TempDir, "fooread.txt")
|
file := filepath.Join(TempDir, "fooread.txt")
|
||||||
|
|
||||||
decl := fmt.Sprintf(`
|
decl := fmt.Sprintf(`
|
||||||
type: file
|
type: file
|
||||||
attributes:
|
attributes:
|
||||||
path: "%s"
|
path: "%s"
|
||||||
@ -51,25 +51,25 @@ func TestNewResourceDeclarationType(t *testing.T) {
|
|||||||
test line 2
|
test line 2
|
||||||
`, file)
|
`, file)
|
||||||
|
|
||||||
resourceDeclaration := NewDeclaration()
|
resourceDeclaration := NewDeclaration()
|
||||||
assert.NotEqual(t, nil, resourceDeclaration)
|
assert.NotEqual(t, nil, resourceDeclaration)
|
||||||
|
|
||||||
resourceDeclaration.LoadDecl(decl)
|
resourceDeclaration.LoadDecl(decl)
|
||||||
assert.Equal(t, "file", resourceDeclaration.Type)
|
assert.Equal(t, "file", resourceDeclaration.Type)
|
||||||
assert.NotEqual(t, nil, resourceDeclaration.Attributes)
|
assert.NotEqual(t, nil, resourceDeclaration.Attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeclarationNewResource(t *testing.T) {
|
func TestDeclarationNewResource(t *testing.T) {
|
||||||
resourceDeclaration := NewDeclaration()
|
resourceDeclaration := NewDeclaration()
|
||||||
assert.NotNil(t, resourceDeclaration)
|
assert.NotNil(t, resourceDeclaration)
|
||||||
|
|
||||||
errNewUnknownResource := resourceDeclaration.NewResource()
|
errNewUnknownResource := resourceDeclaration.NewResource()
|
||||||
assert.ErrorIs(t, errNewUnknownResource, ErrUnknownResourceType)
|
assert.ErrorIs(t, errNewUnknownResource, ErrUnknownResourceType)
|
||||||
|
|
||||||
resourceDeclaration.Type = "file"
|
resourceDeclaration.Type = "file"
|
||||||
errNewFileResource := resourceDeclaration.NewResource()
|
errNewFileResource := resourceDeclaration.NewResource()
|
||||||
assert.Nil(t, errNewFileResource)
|
assert.Nil(t, errNewFileResource)
|
||||||
|
|
||||||
//assert.NotNil(t, resourceDeclaration.Implementation)
|
//assert.NotNil(t, resourceDeclaration.Implementation)
|
||||||
assert.NotNil(t, resourceDeclaration.Attributes)
|
assert.NotNil(t, resourceDeclaration.Attributes)
|
||||||
}
|
}
|
||||||
|
@ -1,69 +1,68 @@
|
|||||||
// 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 (
|
||||||
_ "fmt"
|
_ "fmt"
|
||||||
_ "log"
|
"gopkg.in/yaml.v3"
|
||||||
"io"
|
"io"
|
||||||
"gopkg.in/yaml.v3"
|
_ "log"
|
||||||
_ "net/url"
|
_ "net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Document struct {
|
type Document struct {
|
||||||
ResourceDecls []Declaration `yaml:"resources"`
|
ResourceDecls []Declaration `yaml:"resources"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDocument() *Document {
|
func NewDocument() *Document {
|
||||||
return &Document {}
|
return &Document{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Load(r io.Reader) error {
|
func (d *Document) Load(r io.Reader) error {
|
||||||
yamlDecoder := yaml.NewDecoder(r)
|
yamlDecoder := yaml.NewDecoder(r)
|
||||||
yamlDecoder.Decode(d)
|
yamlDecoder.Decode(d)
|
||||||
for i := range(d.ResourceDecls) {
|
for i := range d.ResourceDecls {
|
||||||
if _,e := d.ResourceDecls[i].LoadResourceFromYaml(); e != nil {
|
if _, e := d.ResourceDecls[i].LoadResourceFromYaml(); e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Resources() []Declaration {
|
func (d *Document) Resources() []Declaration {
|
||||||
return d.ResourceDecls
|
return d.ResourceDecls
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Apply() error {
|
func (d *Document) Apply() error {
|
||||||
for i := range(d.ResourceDecls) {
|
for i := range d.ResourceDecls {
|
||||||
if e := d.ResourceDecls[i].Resource().Apply(); e != nil {
|
if e := d.ResourceDecls[i].Resource().Apply(); e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Generate(w io.Writer) (error) {
|
func (d *Document) Generate(w io.Writer) error {
|
||||||
yamlEncoder := yaml.NewEncoder(w)
|
yamlEncoder := yaml.NewEncoder(w)
|
||||||
yamlEncoder.Encode(d)
|
yamlEncoder.Encode(d)
|
||||||
return yamlEncoder.Close()
|
return yamlEncoder.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) AddResourceDeclaration(resourceType string, resourceDeclaration Resource) {
|
func (d *Document) AddResourceDeclaration(resourceType string, resourceDeclaration Resource) {
|
||||||
decl := NewDeclaration()
|
decl := NewDeclaration()
|
||||||
decl.Type = resourceType
|
decl.Type = resourceType
|
||||||
decl.Implementation = resourceDeclaration
|
decl.Implementation = resourceDeclaration
|
||||||
decl.UpdateYamlFromResource()
|
decl.UpdateYamlFromResource()
|
||||||
d.ResourceDecls = append(d.ResourceDecls, *decl)
|
d.ResourceDecls = append(d.ResourceDecls, *decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) AddResource(uri string) error {
|
func (d *Document) AddResource(uri string) error {
|
||||||
//parsedResourceURI, e := url.Parse(uri)
|
//parsedResourceURI, e := url.Parse(uri)
|
||||||
//if e == nil {
|
//if e == nil {
|
||||||
decl := NewDeclaration()
|
decl := NewDeclaration()
|
||||||
decl.SetURI(uri)
|
decl.SetURI(uri)
|
||||||
decl.UpdateYamlFromResource()
|
decl.UpdateYamlFromResource()
|
||||||
d.ResourceDecls = append(d.ResourceDecls, *decl)
|
d.ResourceDecls = append(d.ResourceDecls, *decl)
|
||||||
//}
|
//}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2,33 +2,33 @@
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"fmt"
|
||||||
"fmt"
|
"github.com/stretchr/testify/assert"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"strings"
|
||||||
"github.com/stretchr/testify/assert"
|
"syscall"
|
||||||
"time"
|
"testing"
|
||||||
"syscall"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewDocumentLoader(t *testing.T) {
|
func TestNewDocumentLoader(t *testing.T) {
|
||||||
d := NewDocument()
|
d := NewDocument()
|
||||||
assert.NotEqual(t, nil, d)
|
assert.NotEqual(t, nil, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDocumentLoader(t *testing.T) {
|
func TestDocumentLoader(t *testing.T) {
|
||||||
dir, err := os.MkdirTemp("", "testdocumentloader")
|
dir, err := os.MkdirTemp("", "testdocumentloader")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
file,_ := filepath.Abs(filepath.Join(dir, "foo.txt"))
|
file, _ := filepath.Abs(filepath.Join(dir, "foo.txt"))
|
||||||
|
|
||||||
document := fmt.Sprintf(`
|
document := fmt.Sprintf(`
|
||||||
---
|
---
|
||||||
resources:
|
resources:
|
||||||
- type: file
|
- type: file
|
||||||
@ -50,39 +50,39 @@ resources:
|
|||||||
state: present
|
state: present
|
||||||
`, file)
|
`, file)
|
||||||
|
|
||||||
d := NewDocument()
|
d := NewDocument()
|
||||||
assert.NotEqual(t, nil, d)
|
assert.NotEqual(t, nil, d)
|
||||||
|
|
||||||
docReader := strings.NewReader(document)
|
docReader := strings.NewReader(document)
|
||||||
|
|
||||||
e := d.Load(docReader)
|
e := d.Load(docReader)
|
||||||
assert.Equal(t, nil, e)
|
assert.Equal(t, nil, e)
|
||||||
|
|
||||||
resources := d.Resources()
|
resources := d.Resources()
|
||||||
assert.Equal(t, 2, len(resources))
|
assert.Equal(t, 2, len(resources))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDocumentGenerator(t *testing.T) {
|
func TestDocumentGenerator(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
fileContent := `// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
fileContent := `// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
`
|
`
|
||||||
|
|
||||||
file,_ := filepath.Abs(filepath.Join(TempDir, "foo.txt"))
|
file, _ := filepath.Abs(filepath.Join(TempDir, "foo.txt"))
|
||||||
|
|
||||||
err := os.WriteFile(file, []byte(fileContent), 0644)
|
err := os.WriteFile(file, []byte(fileContent), 0644)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
info,statErr := os.Stat(file)
|
info, statErr := os.Stat(file)
|
||||||
assert.Nil(t, statErr)
|
assert.Nil(t, statErr)
|
||||||
mTime := info.ModTime()
|
mTime := info.ModTime()
|
||||||
stat, ok := info.Sys().(*syscall.Stat_t)
|
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
|
||||||
aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
||||||
cTime := time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
|
cTime := time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
|
||||||
|
|
||||||
expected := fmt.Sprintf(`
|
expected := fmt.Sprintf(`
|
||||||
resources:
|
resources:
|
||||||
- type: file
|
- type: file
|
||||||
attributes:
|
attributes:
|
||||||
@ -99,31 +99,31 @@ resources:
|
|||||||
state: present
|
state: present
|
||||||
`, file, fileContent, aTime.Format(time.RFC3339Nano), cTime.Format(time.RFC3339Nano), mTime.Format(time.RFC3339Nano))
|
`, file, fileContent, aTime.Format(time.RFC3339Nano), cTime.Format(time.RFC3339Nano), mTime.Format(time.RFC3339Nano))
|
||||||
|
|
||||||
var documentYaml strings.Builder
|
var documentYaml strings.Builder
|
||||||
d := NewDocument()
|
d := NewDocument()
|
||||||
assert.NotEqual(t, nil, d)
|
assert.NotEqual(t, nil, d)
|
||||||
|
|
||||||
f,e := ResourceTypes.New("file://")
|
f, e := ResourceTypes.New("file://")
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
assert.NotNil(t, f)
|
assert.NotNil(t, f)
|
||||||
|
|
||||||
f.(*File).Path = filepath.Join(TempDir, "foo.txt")
|
f.(*File).Path = filepath.Join(TempDir, "foo.txt")
|
||||||
f.(*File).Read(ctx)
|
f.(*File).Read(ctx)
|
||||||
d.AddResourceDeclaration("file", f)
|
d.AddResourceDeclaration("file", f)
|
||||||
|
|
||||||
ey := d.Generate(&documentYaml)
|
ey := d.Generate(&documentYaml)
|
||||||
assert.Equal(t, nil, ey)
|
assert.Equal(t, nil, ey)
|
||||||
|
|
||||||
assert.Greater(t, documentYaml.Len(), 0)
|
assert.Greater(t, documentYaml.Len(), 0)
|
||||||
assert.YAMLEq(t, expected, documentYaml.String())
|
assert.YAMLEq(t, expected, documentYaml.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDocumentAddResource(t *testing.T) {
|
func TestDocumentAddResource(t *testing.T) {
|
||||||
file,_ := filepath.Abs(filepath.Join(TempDir, "foo.txt"))
|
file, _ := filepath.Abs(filepath.Join(TempDir, "foo.txt"))
|
||||||
err := os.WriteFile(file, []byte(""), 0644)
|
err := os.WriteFile(file, []byte(""), 0644)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
d := NewDocument()
|
d := NewDocument()
|
||||||
assert.NotNil(t, d)
|
assert.NotNil(t, d)
|
||||||
d.AddResource(fmt.Sprintf("file://%s", file))
|
d.AddResource(fmt.Sprintf("file://%s", file))
|
||||||
}
|
}
|
||||||
|
@ -1,65 +1,63 @@
|
|||||||
// 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 (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "os"
|
"gopkg.in/yaml.v3"
|
||||||
"gopkg.in/yaml.v3"
|
_ "log"
|
||||||
_ "os/exec"
|
"net/url"
|
||||||
_ "strings"
|
_ "os"
|
||||||
_ "log"
|
_ "os/exec"
|
||||||
"net/url"
|
_ "strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Exec struct {
|
type Exec struct {
|
||||||
loader YamlLoader
|
loader YamlLoader
|
||||||
Id string `yaml:"id"`
|
Id string `yaml:"id"`
|
||||||
// create command
|
// create command
|
||||||
// read command
|
// read command
|
||||||
// update command
|
// update command
|
||||||
// delete command
|
// delete command
|
||||||
|
|
||||||
// state attributes
|
// state attributes
|
||||||
State string `yaml:"state"`
|
State string `yaml:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ResourceTypes.Register("exec", func(u *url.URL) Resource {
|
ResourceTypes.Register("exec", func(u *url.URL) Resource {
|
||||||
x := NewExec()
|
x := NewExec()
|
||||||
return x
|
return x
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExec() *Exec {
|
func NewExec() *Exec {
|
||||||
return &Exec { loader: YamlLoadDecl }
|
return &Exec{loader: YamlLoadDecl}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) URI() string {
|
func (x *Exec) URI() string {
|
||||||
return fmt.Sprintf("exec://%s", x.Id)
|
return fmt.Sprintf("exec://%s", x.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) SetURI(uri string) error {
|
func (x *Exec) SetURI(uri string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) ResolveId(ctx context.Context) string {
|
func (x *Exec) ResolveId(ctx context.Context) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) Apply() error {
|
func (x *Exec) Apply() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) LoadDecl(yamlFileResourceDeclaration string) error {
|
func (x *Exec) LoadDecl(yamlFileResourceDeclaration string) error {
|
||||||
return x.loader(yamlFileResourceDeclaration, x)
|
return x.loader(yamlFileResourceDeclaration, x)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) Type() string { return "exec" }
|
func (x *Exec) Type() string { return "exec" }
|
||||||
|
|
||||||
func (x *Exec) Read(ctx context.Context) ([]byte, error) {
|
func (x *Exec) Read(ctx context.Context) ([]byte, error) {
|
||||||
return yaml.Marshal(x)
|
return yaml.Marshal(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,9 +41,9 @@ func TestCreateExec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExecSetURI(t *testing.T) {
|
func TestExecSetURI(t *testing.T) {
|
||||||
x := NewExec()
|
x := NewExec()
|
||||||
assert.NotNil(t, x)
|
assert.NotNil(t, x)
|
||||||
x.SetURI("exec://" + "12345_key")
|
x.SetURI("exec://" + "12345_key")
|
||||||
assert.Equal(t, "exec", x.Type())
|
assert.Equal(t, "exec", x.Type())
|
||||||
assert.Equal(t, "12345_key", x.Id)
|
assert.Equal(t, "12345_key", x.Id)
|
||||||
}
|
}
|
||||||
|
@ -1,222 +1,222 @@
|
|||||||
// 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 (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"gopkg.in/yaml.v3"
|
||||||
"os/user"
|
"io"
|
||||||
"io"
|
"net/url"
|
||||||
"syscall"
|
"os"
|
||||||
"gopkg.in/yaml.v3"
|
"os/user"
|
||||||
"strconv"
|
"path/filepath"
|
||||||
"path/filepath"
|
"strconv"
|
||||||
"net/url"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileType string
|
type FileType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RegularFile FileType = "regular"
|
RegularFile FileType = "regular"
|
||||||
DirectoryFile FileType = "directory"
|
DirectoryFile FileType = "directory"
|
||||||
BlockDeviceFile FileType = "block"
|
BlockDeviceFile FileType = "block"
|
||||||
CharacterDeviceFile FileType = "char"
|
CharacterDeviceFile FileType = "char"
|
||||||
NamedPipeFile FileType = "pipe"
|
NamedPipeFile FileType = "pipe"
|
||||||
SymbolicLinkFile FileType = "symlink"
|
SymbolicLinkFile FileType = "symlink"
|
||||||
SocketFile FileType = "socket"
|
SocketFile FileType = "socket"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrInvalidResourceURI error = errors.New("Invalid resource URI")
|
var ErrInvalidResourceURI error = errors.New("Invalid resource URI")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ResourceTypes.Register("file", func(u *url.URL) Resource {
|
ResourceTypes.Register("file", func(u *url.URL) Resource {
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
f.Path = filepath.Join(u.Hostname(), u.Path)
|
f.Path = filepath.Join(u.Hostname(), u.Path)
|
||||||
return f
|
return f
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manage the state of file system objects
|
// Manage the state of file system objects
|
||||||
type File struct {
|
type File struct {
|
||||||
loader YamlLoader
|
loader YamlLoader
|
||||||
Path string `yaml:"path"`
|
Path string `yaml:"path"`
|
||||||
Owner string `yaml:"owner"`
|
Owner string `yaml:"owner"`
|
||||||
Group string `yaml:"group"`
|
Group string `yaml:"group"`
|
||||||
Mode string `yaml:"mode"`
|
Mode string `yaml:"mode"`
|
||||||
|
|
||||||
Atime time.Time `yaml:"atime",omitempty`
|
Atime time.Time `yaml:"atime",omitempty`
|
||||||
Ctime time.Time `yaml:"ctime",omitempty`
|
Ctime time.Time `yaml:"ctime",omitempty`
|
||||||
Mtime time.Time `yaml:"mtime",omitempty`
|
Mtime time.Time `yaml:"mtime",omitempty`
|
||||||
|
|
||||||
Content string `yaml:"content",omitempty`
|
Content string `yaml:"content",omitempty`
|
||||||
FileType FileType `yaml:"filetype"`
|
FileType FileType `yaml:"filetype"`
|
||||||
State string `yaml:"state"`
|
State string `yaml:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFile() *File {
|
func NewFile() *File {
|
||||||
return &File{ loader: YamlLoadDecl, FileType: RegularFile }
|
return &File{loader: YamlLoadDecl, FileType: RegularFile}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) URI() string {
|
func (f *File) URI() string {
|
||||||
return fmt.Sprintf("file://%s", f.Path)
|
return fmt.Sprintf("file://%s", f.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) SetURI(uri string) error {
|
func (f *File) SetURI(uri string) error {
|
||||||
resourceUri, e := url.Parse(uri)
|
resourceUri, e := url.Parse(uri)
|
||||||
if resourceUri.Scheme == "file" {
|
if resourceUri.Scheme == "file" {
|
||||||
f.Path, e = filepath.Abs(filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI()))
|
f.Path, e = filepath.Abs(filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI()))
|
||||||
} else {
|
} else {
|
||||||
e = fmt.Errorf("%w: %s is not a file", ErrInvalidResourceURI, uri)
|
e = fmt.Errorf("%w: %s is not a file", ErrInvalidResourceURI, uri)
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Apply() error {
|
func (f *File) Apply() error {
|
||||||
|
|
||||||
switch f.State {
|
switch f.State {
|
||||||
case "absent":
|
case "absent":
|
||||||
removeErr := os.Remove(f.Path)
|
removeErr := os.Remove(f.Path)
|
||||||
if removeErr != nil {
|
if removeErr != nil {
|
||||||
return removeErr
|
return removeErr
|
||||||
}
|
}
|
||||||
case "present": {
|
case "present":
|
||||||
uid,uidErr := LookupUID(f.Owner)
|
{
|
||||||
if uidErr != nil {
|
uid, uidErr := LookupUID(f.Owner)
|
||||||
return uidErr
|
if uidErr != nil {
|
||||||
}
|
return uidErr
|
||||||
|
}
|
||||||
|
|
||||||
gid,gidErr := LookupGID(f.Group)
|
gid, gidErr := LookupGID(f.Group)
|
||||||
if gidErr != nil {
|
if gidErr != nil {
|
||||||
return gidErr
|
return gidErr
|
||||||
}
|
}
|
||||||
|
|
||||||
mode,modeErr := strconv.ParseInt(f.Mode, 8, 64)
|
mode, modeErr := strconv.ParseInt(f.Mode, 8, 64)
|
||||||
if modeErr != nil {
|
if modeErr != nil {
|
||||||
return modeErr
|
return modeErr
|
||||||
}
|
}
|
||||||
|
|
||||||
//e := os.Stat(f.path)
|
//e := os.Stat(f.path)
|
||||||
//if os.IsNotExist(e) {
|
//if os.IsNotExist(e) {
|
||||||
switch f.FileType {
|
switch f.FileType {
|
||||||
case DirectoryFile:
|
case DirectoryFile:
|
||||||
os.MkdirAll(f.Path, os.FileMode(mode))
|
os.MkdirAll(f.Path, os.FileMode(mode))
|
||||||
default:
|
default:
|
||||||
fallthrough
|
fallthrough
|
||||||
case RegularFile:
|
case RegularFile:
|
||||||
createdFile,e := os.Create(f.Path)
|
createdFile, e := os.Create(f.Path)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
defer createdFile.Close()
|
defer createdFile.Close()
|
||||||
|
|
||||||
if chmodErr := createdFile.Chmod(os.FileMode(mode)); chmodErr != nil {
|
if chmodErr := createdFile.Chmod(os.FileMode(mode)); chmodErr != nil {
|
||||||
return chmodErr
|
return chmodErr
|
||||||
}
|
}
|
||||||
_,writeErr := createdFile.Write([]byte(f.Content))
|
_, writeErr := createdFile.Write([]byte(f.Content))
|
||||||
if writeErr != nil {
|
if writeErr != nil {
|
||||||
return writeErr
|
return writeErr
|
||||||
}
|
}
|
||||||
if ! f.Mtime.IsZero() && ! f.Atime.IsZero() {
|
if !f.Mtime.IsZero() && !f.Atime.IsZero() {
|
||||||
if chtimesErr := os.Chtimes(f.Path, f.Atime, f.Mtime); chtimesErr != nil {
|
if chtimesErr := os.Chtimes(f.Path, f.Atime, f.Mtime); chtimesErr != nil {
|
||||||
return chtimesErr
|
return chtimesErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if chownErr := os.Chown(f.Path, uid, gid); chownErr != nil {
|
if chownErr := os.Chown(f.Path, uid, gid); chownErr != nil {
|
||||||
return chownErr
|
return chownErr
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) LoadDecl(yamlFileResourceDeclaration string) error {
|
func (f *File) LoadDecl(yamlFileResourceDeclaration string) error {
|
||||||
return f.loader(yamlFileResourceDeclaration, f)
|
return f.loader(yamlFileResourceDeclaration, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) ResolveId(ctx context.Context) string {
|
func (f *File) ResolveId(ctx context.Context) string {
|
||||||
filePath, fileAbsErr := filepath.Abs(f.Path)
|
filePath, fileAbsErr := filepath.Abs(f.Path)
|
||||||
if fileAbsErr != nil {
|
if fileAbsErr != nil {
|
||||||
panic(fileAbsErr)
|
panic(fileAbsErr)
|
||||||
}
|
}
|
||||||
f.Path = filePath
|
f.Path = filePath
|
||||||
return filePath
|
return filePath
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Read(ctx context.Context) ([]byte, error) {
|
func (f *File) Read(ctx context.Context) ([]byte, error) {
|
||||||
filePath, fileAbsErr := filepath.Abs(f.Path)
|
filePath, fileAbsErr := filepath.Abs(f.Path)
|
||||||
if fileAbsErr != nil {
|
if fileAbsErr != nil {
|
||||||
panic(fileAbsErr)
|
panic(fileAbsErr)
|
||||||
}
|
}
|
||||||
f.Path = filePath
|
f.Path = filePath
|
||||||
|
|
||||||
info, e := os.Stat(f.Path)
|
info, e := os.Stat(f.Path)
|
||||||
|
|
||||||
if e != nil {
|
if e != nil {
|
||||||
f.State = "absent"
|
f.State = "absent"
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Mtime = info.ModTime()
|
f.Mtime = info.ModTime()
|
||||||
|
|
||||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||||
f.Atime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
f.Atime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
||||||
f.Ctime = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
|
f.Ctime = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
|
||||||
|
|
||||||
userId := strconv.Itoa(int(stat.Uid))
|
userId := strconv.Itoa(int(stat.Uid))
|
||||||
groupId := strconv.Itoa(int(stat.Gid))
|
groupId := strconv.Itoa(int(stat.Gid))
|
||||||
fileUser, userErr := user.LookupId(userId)
|
fileUser, userErr := user.LookupId(userId)
|
||||||
if userErr != nil { //UnknownUserIdError
|
if userErr != nil { //UnknownUserIdError
|
||||||
//panic(userErr)
|
//panic(userErr)
|
||||||
f.Owner = userId
|
f.Owner = userId
|
||||||
} else {
|
} else {
|
||||||
f.Owner = fileUser.Name
|
f.Owner = fileUser.Name
|
||||||
}
|
}
|
||||||
fileGroup, groupErr := user.LookupGroupId(groupId)
|
fileGroup, groupErr := user.LookupGroupId(groupId)
|
||||||
if groupErr != nil {
|
if groupErr != nil {
|
||||||
//panic(groupErr)
|
//panic(groupErr)
|
||||||
f.Group = groupId
|
f.Group = groupId
|
||||||
} else {
|
} else {
|
||||||
f.Group = fileGroup.Name
|
f.Group = fileGroup.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Mode = fmt.Sprintf("%04o", info.Mode().Perm())
|
f.Mode = fmt.Sprintf("%04o", info.Mode().Perm())
|
||||||
|
|
||||||
file, fileErr := os.Open(f.Path)
|
file, fileErr := os.Open(f.Path)
|
||||||
if fileErr != nil {
|
if fileErr != nil {
|
||||||
panic(fileErr)
|
panic(fileErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileContent, ioErr := io.ReadAll(file)
|
fileContent, ioErr := io.ReadAll(file)
|
||||||
if ioErr != nil {
|
if ioErr != nil {
|
||||||
panic(ioErr)
|
panic(ioErr)
|
||||||
}
|
}
|
||||||
f.Content = string(fileContent)
|
f.Content = string(fileContent)
|
||||||
f.State = "present"
|
f.State = "present"
|
||||||
return yaml.Marshal(f)
|
return yaml.Marshal(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Type() string { return "file" }
|
func (f *File) Type() string { return "file" }
|
||||||
|
|
||||||
func (f *FileType) UnmarshalYAML(value *yaml.Node) error {
|
func (f *FileType) UnmarshalYAML(value *yaml.Node) error {
|
||||||
var s string
|
var s string
|
||||||
if err := value.Decode(&s); err != nil {
|
if err := value.Decode(&s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch s {
|
switch s {
|
||||||
case string(RegularFile), string(DirectoryFile), string(BlockDeviceFile), string(CharacterDeviceFile), string(NamedPipeFile), string(SymbolicLinkFile), string(SocketFile):
|
case string(RegularFile), string(DirectoryFile), string(BlockDeviceFile), string(CharacterDeviceFile), string(NamedPipeFile), string(SymbolicLinkFile), string(SocketFile):
|
||||||
*f = FileType(s)
|
*f = FileType(s)
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return errors.New("invalid FileType value")
|
return errors.New("invalid FileType value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
_ "strings"
|
_ "strings"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewFileResource(t *testing.T) {
|
func TestNewFileResource(t *testing.T) {
|
||||||
@ -67,26 +67,26 @@ func TestReadFile(t *testing.T) {
|
|||||||
assert.Equal(t, nil, e)
|
assert.Equal(t, nil, e)
|
||||||
assert.Equal(t, "nobody", f.Owner)
|
assert.Equal(t, "nobody", f.Owner)
|
||||||
|
|
||||||
info,statErr := os.Stat(file)
|
info, statErr := os.Stat(file)
|
||||||
assert.Nil(t, statErr)
|
assert.Nil(t, statErr)
|
||||||
stat, ok := info.Sys().(*syscall.Stat_t)
|
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
cTime := time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
|
cTime := time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
|
||||||
|
|
||||||
expected := fmt.Sprintf(declarationAttributes, file, cTime.Format(time.RFC3339Nano))
|
expected := fmt.Sprintf(declarationAttributes, file, cTime.Format(time.RFC3339Nano))
|
||||||
assert.YAMLEq(t, expected, string(r))
|
assert.YAMLEq(t, expected, string(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadFileError(t *testing.T) {
|
func TestReadFileError(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
file, _ := filepath.Abs(filepath.Join(TempDir, "missingfile.txt"))
|
file, _ := filepath.Abs(filepath.Join(TempDir, "missingfile.txt"))
|
||||||
|
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
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.True(t, os.IsNotExist(e))
|
||||||
assert.Equal(t, "absent", f.State)
|
assert.Equal(t, "absent", f.State)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateFile(t *testing.T) {
|
func TestCreateFile(t *testing.T) {
|
||||||
@ -159,8 +159,8 @@ func TestFileDirectory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileTimes(t *testing.T) {
|
func TestFileTimes(t *testing.T) {
|
||||||
file, _ := filepath.Abs(filepath.Join(TempDir, "testtimes.txt"))
|
file, _ := filepath.Abs(filepath.Join(TempDir, "testtimes.txt"))
|
||||||
decl := fmt.Sprintf(`
|
decl := fmt.Sprintf(`
|
||||||
path: "%s"
|
path: "%s"
|
||||||
owner: "nobody"
|
owner: "nobody"
|
||||||
group: "nobody"
|
group: "nobody"
|
||||||
@ -170,21 +170,21 @@ func TestFileTimes(t *testing.T) {
|
|||||||
state: "present"
|
state: "present"
|
||||||
`, file)
|
`, file)
|
||||||
|
|
||||||
expectedTime, timeErr := time.Parse(time.RFC3339, "2001-12-15T01:01:01.1Z")
|
expectedTime, timeErr := time.Parse(time.RFC3339, "2001-12-15T01:01:01.1Z")
|
||||||
assert.Nil(t, timeErr)
|
assert.Nil(t, timeErr)
|
||||||
|
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
e := f.LoadDecl(decl)
|
e := f.LoadDecl(decl)
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
assert.Equal(t, "nobody", f.Owner)
|
assert.Equal(t, "nobody", f.Owner)
|
||||||
assert.True(t, f.Mtime.Equal(expectedTime))
|
assert.True(t, f.Mtime.Equal(expectedTime))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileSetURI(t *testing.T) {
|
func TestFileSetURI(t *testing.T) {
|
||||||
file, _ := filepath.Abs(filepath.Join(TempDir, "testuri.txt"))
|
file, _ := filepath.Abs(filepath.Join(TempDir, "testuri.txt"))
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
assert.NotNil(t, f)
|
assert.NotNil(t, f)
|
||||||
f.SetURI("file://" + file)
|
f.SetURI("file://" + file)
|
||||||
assert.Equal(t, "file", f.Type())
|
assert.Equal(t, "file", f.Type())
|
||||||
assert.Equal(t, file, f.Path)
|
assert.Equal(t, file, f.Path)
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,44 @@
|
|||||||
// 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 (
|
||||||
"os/user"
|
"os/user"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LookupUIDString(userName string) string {
|
func LookupUIDString(userName string) string {
|
||||||
user, userLookupErr := user.Lookup(userName)
|
user, userLookupErr := user.Lookup(userName)
|
||||||
if userLookupErr != nil {
|
if userLookupErr != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return user.Uid
|
return user.Uid
|
||||||
}
|
}
|
||||||
|
|
||||||
func LookupUID(userName string) (int,error) {
|
func LookupUID(userName string) (int, error) {
|
||||||
user, userLookupErr := user.Lookup(userName)
|
user, userLookupErr := user.Lookup(userName)
|
||||||
if userLookupErr != nil {
|
if userLookupErr != nil {
|
||||||
return -1,userLookupErr
|
return -1, userLookupErr
|
||||||
}
|
}
|
||||||
|
|
||||||
uid, uidErr := strconv.Atoi(user.Uid)
|
uid, uidErr := strconv.Atoi(user.Uid)
|
||||||
if uidErr != nil {
|
if uidErr != nil {
|
||||||
return -1,uidErr
|
return -1, uidErr
|
||||||
}
|
}
|
||||||
|
|
||||||
return uid,nil
|
return uid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LookupGID(groupName string) (int,error) {
|
func LookupGID(groupName string) (int, error) {
|
||||||
group, groupLookupErr := user.LookupGroup(groupName)
|
group, groupLookupErr := user.LookupGroup(groupName)
|
||||||
if groupLookupErr != nil {
|
if groupLookupErr != nil {
|
||||||
return -1,groupLookupErr
|
return -1, groupLookupErr
|
||||||
}
|
}
|
||||||
|
|
||||||
gid, gidErr := strconv.Atoi(group.Gid)
|
gid, gidErr := strconv.Atoi(group.Gid)
|
||||||
if gidErr != nil {
|
if gidErr != nil {
|
||||||
return -1,gidErr
|
return -1, gidErr
|
||||||
}
|
}
|
||||||
|
|
||||||
return gid, nil
|
return gid, nil
|
||||||
}
|
}
|
||||||
|
@ -2,30 +2,30 @@
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "fmt"
|
_ "context"
|
||||||
_ "context"
|
_ "encoding/json"
|
||||||
"testing"
|
_ "fmt"
|
||||||
_ "net/http"
|
"github.com/stretchr/testify/assert"
|
||||||
_ "net/http/httptest"
|
_ "io"
|
||||||
_ "net/url"
|
_ "net/http"
|
||||||
_ "io"
|
_ "net/http/httptest"
|
||||||
"github.com/stretchr/testify/assert"
|
_ "net/url"
|
||||||
_ "encoding/json"
|
_ "strings"
|
||||||
_ "strings"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLookupUID(t *testing.T) {
|
func TestLookupUID(t *testing.T) {
|
||||||
uid,e := LookupUID("nobody")
|
uid, e := LookupUID("nobody")
|
||||||
|
|
||||||
assert.Equal(t, nil, e)
|
assert.Equal(t, nil, e)
|
||||||
assert.Equal(t, 65534, uid)
|
assert.Equal(t, 65534, uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLookupGID(t *testing.T) {
|
func TestLookupGID(t *testing.T) {
|
||||||
gid,e := LookupGID("nobody")
|
gid, e := LookupGID("nobody")
|
||||||
|
|
||||||
assert.Equal(t, nil, e)
|
assert.Equal(t, nil, e)
|
||||||
assert.Equal(t, 65534, gid)
|
assert.Equal(t, 65534, gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecCommand(t *testing.T) {
|
func TestExecCommand(t *testing.T) {
|
||||||
|
@ -4,51 +4,50 @@
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "fmt"
|
_ "fmt"
|
||||||
_ "gopkg.in/yaml.v3"
|
_ "gopkg.in/yaml.v3"
|
||||||
_ "net/url"
|
_ "net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Resource interface {
|
type Resource interface {
|
||||||
Type() string
|
Type() string
|
||||||
URI() string
|
URI() string
|
||||||
//SetURI(string) error
|
//SetURI(string) error
|
||||||
ResolveId(context.Context) string
|
ResolveId(context.Context) string
|
||||||
ResourceLoader
|
ResourceLoader
|
||||||
StateTransformer
|
StateTransformer
|
||||||
ResourceReader
|
ResourceReader
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate the type/uri
|
// validate the type/uri
|
||||||
type ResourceValidator interface {
|
type ResourceValidator interface {
|
||||||
Validate() error
|
Validate() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceCreator interface {
|
type ResourceCreator interface {
|
||||||
Create(context.Context) error
|
Create(context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceReader interface {
|
type ResourceReader interface {
|
||||||
Read(context.Context) ([]byte, error)
|
Read(context.Context) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceUpdater interface {
|
type ResourceUpdater interface {
|
||||||
Update() error
|
Update() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceDeleter interface {
|
type ResourceDeleter interface {
|
||||||
Delete() error
|
Delete() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceDecoder struct {
|
type ResourceDecoder struct {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResource(uri string) Resource {
|
func NewResource(uri string) Resource {
|
||||||
r,e := ResourceTypes.New(uri)
|
r, e := ResourceTypes.New(uri)
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2,45 +2,45 @@
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
_ "fmt"
|
||||||
"path/filepath"
|
"github.com/stretchr/testify/assert"
|
||||||
_ "fmt"
|
"log"
|
||||||
"log"
|
"os"
|
||||||
"testing"
|
"path/filepath"
|
||||||
"github.com/stretchr/testify/assert"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var TempDir string
|
var TempDir string
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
var err error
|
var err error
|
||||||
TempDir, err = os.MkdirTemp("", "testresourcefile")
|
TempDir, err = os.MkdirTemp("", "testresourcefile")
|
||||||
if err != nil || TempDir == "" {
|
if err != nil || TempDir == "" {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rc := m.Run()
|
rc := m.Run()
|
||||||
|
|
||||||
os.RemoveAll(TempDir)
|
os.RemoveAll(TempDir)
|
||||||
os.Exit(rc)
|
os.Exit(rc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewResource(t *testing.T) {
|
func TestNewResource(t *testing.T) {
|
||||||
resourceUri := "file://foo"
|
resourceUri := "file://foo"
|
||||||
testFile := NewResource(resourceUri)
|
testFile := NewResource(resourceUri)
|
||||||
assert.NotNil(t, testFile)
|
assert.NotNil(t, testFile)
|
||||||
|
|
||||||
assert.Equal(t, "foo", testFile.(*File).Path)
|
assert.Equal(t, "foo", testFile.(*File).Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveId(t *testing.T) {
|
func TestResolveId(t *testing.T) {
|
||||||
testFile := NewResource("file://../../README.md")
|
testFile := NewResource("file://../../README.md")
|
||||||
assert.NotNil(t, testFile)
|
assert.NotNil(t, testFile)
|
||||||
|
|
||||||
absolutePath,e := filepath.Abs("../../README.md")
|
absolutePath, e := filepath.Abs("../../README.md")
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
|
|
||||||
testFile.ResolveId(context.Background())
|
testFile.ResolveId(context.Background())
|
||||||
assert.Equal(t, absolutePath, testFile.(*File).Path)
|
assert.Equal(t, absolutePath, testFile.(*File).Path)
|
||||||
}
|
}
|
||||||
|
@ -1,48 +1,47 @@
|
|||||||
// 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 (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrUnknownResourceType = errors.New("Unknown resource type")
|
ErrUnknownResourceType = errors.New("Unknown resource type")
|
||||||
ResourceTypes *Types = NewTypes()
|
ResourceTypes *Types = NewTypes()
|
||||||
)
|
)
|
||||||
|
|
||||||
type TypeFactory func(*url.URL) Resource
|
type TypeFactory func(*url.URL) Resource
|
||||||
|
|
||||||
type Types struct {
|
type Types struct {
|
||||||
registry map[string]TypeFactory
|
registry map[string]TypeFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTypes() *Types {
|
func NewTypes() *Types {
|
||||||
return &Types{ registry: make(map[string]TypeFactory) }
|
return &Types{registry: make(map[string]TypeFactory)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Types) Register(name string, factory TypeFactory) {
|
func (t *Types) Register(name string, factory TypeFactory) {
|
||||||
t.registry[name] = factory
|
t.registry[name] = factory
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Types) New(uri string) (Resource, error) {
|
func (t *Types) New(uri string) (Resource, error) {
|
||||||
u,e := url.Parse(uri)
|
u, e := url.Parse(uri)
|
||||||
if u == nil || e != nil {
|
if u == nil || e != nil {
|
||||||
return nil, fmt.Errorf("%w: %s", ErrUnknownResourceType, e)
|
return nil, fmt.Errorf("%w: %s", ErrUnknownResourceType, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r,ok := t.registry[u.Scheme]; ok {
|
if r, ok := t.registry[u.Scheme]; ok {
|
||||||
return r(u), nil
|
return r(u), nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("%w: %s", ErrUnknownResourceType, u.Scheme)
|
return nil, fmt.Errorf("%w: %s", ErrUnknownResourceType, u.Scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Types) Has(typename string) bool {
|
func (t *Types) Has(typename string) bool {
|
||||||
if _,ok := t.registry[typename]; ok {
|
if _, ok := t.registry[typename]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -2,52 +2,52 @@
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "context"
|
_ "context"
|
||||||
"testing"
|
"decl/tests/mocks"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"decl/tests/mocks"
|
"net/url"
|
||||||
"net/url"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewResourceTypes(t *testing.T) {
|
func TestNewResourceTypes(t *testing.T) {
|
||||||
resourceTypes := NewTypes()
|
resourceTypes := NewTypes()
|
||||||
assert.NotEqual(t, nil, resourceTypes)
|
assert.NotEqual(t, nil, resourceTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewResourceTypesRegister(t *testing.T) {
|
func TestNewResourceTypesRegister(t *testing.T) {
|
||||||
m := mocks.NewFooResource()
|
m := mocks.NewFooResource()
|
||||||
|
|
||||||
resourceTypes := NewTypes()
|
resourceTypes := NewTypes()
|
||||||
assert.NotEqual(t, nil, resourceTypes)
|
assert.NotEqual(t, nil, resourceTypes)
|
||||||
|
|
||||||
resourceTypes.Register("foo", func(*url.URL) Resource { return m })
|
resourceTypes.Register("foo", func(*url.URL) Resource { return m })
|
||||||
|
|
||||||
r,e := resourceTypes.New("foo://")
|
r, e := resourceTypes.New("foo://")
|
||||||
assert.Equal(t, nil, e)
|
assert.Equal(t, nil, e)
|
||||||
assert.Equal(t, m, r)
|
assert.Equal(t, m, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourceTypesFromURI(t *testing.T) {
|
func TestResourceTypesFromURI(t *testing.T) {
|
||||||
m := mocks.NewFooResource()
|
m := mocks.NewFooResource()
|
||||||
|
|
||||||
resourceTypes := NewTypes()
|
resourceTypes := NewTypes()
|
||||||
assert.NotEqual(t, nil, resourceTypes)
|
assert.NotEqual(t, nil, resourceTypes)
|
||||||
|
|
||||||
resourceTypes.Register("foo", func(*url.URL) Resource { return m })
|
resourceTypes.Register("foo", func(*url.URL) Resource { return m })
|
||||||
|
|
||||||
r,e := resourceTypes.New("foo://bar")
|
r, e := resourceTypes.New("foo://bar")
|
||||||
assert.Equal(t, nil, e)
|
assert.Equal(t, nil, e)
|
||||||
assert.Equal(t, m, r)
|
assert.Equal(t, m, r)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourceTypesHasType(t *testing.T) {
|
func TestResourceTypesHasType(t *testing.T) {
|
||||||
m := mocks.NewFooResource()
|
m := mocks.NewFooResource()
|
||||||
|
|
||||||
resourceTypes := NewTypes()
|
resourceTypes := NewTypes()
|
||||||
assert.NotNil(t, resourceTypes)
|
assert.NotNil(t, resourceTypes)
|
||||||
|
|
||||||
resourceTypes.Register("foo", func(*url.URL) Resource { return m })
|
resourceTypes.Register("foo", func(*url.URL) Resource { return m })
|
||||||
|
|
||||||
assert.True(t, resourceTypes.Has("foo"))
|
assert.True(t, resourceTypes.Has("foo"))
|
||||||
}
|
}
|
||||||
|
@ -1,156 +1,154 @@
|
|||||||
// 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 (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "os"
|
"gopkg.in/yaml.v3"
|
||||||
"gopkg.in/yaml.v3"
|
"log"
|
||||||
"os/exec"
|
"net/url"
|
||||||
"strings"
|
_ "os"
|
||||||
"log"
|
"os/exec"
|
||||||
"net/url"
|
"os/user"
|
||||||
"os/user"
|
"strconv"
|
||||||
"strconv"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
loader YamlLoader
|
loader YamlLoader
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
UID int `yaml:"uid"`
|
UID int `yaml:"uid"`
|
||||||
Group string `yaml:"group"`
|
Group string `yaml:"group"`
|
||||||
Groups []string `yaml:"groups",omitempty`
|
Groups []string `yaml:"groups",omitempty`
|
||||||
Gecos string `yaml:"gecos"`
|
Gecos string `yaml:"gecos"`
|
||||||
Home string `yaml:"home"`
|
Home string `yaml:"home"`
|
||||||
CreateHome bool `yaml:"createhome"omitempty`
|
CreateHome bool `yaml:"createhome"omitempty`
|
||||||
Shell string `yaml:"shell"`
|
Shell string `yaml:"shell"`
|
||||||
|
|
||||||
State string `yaml:"state"`
|
State string `yaml:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUser() *User {
|
func NewUser() *User {
|
||||||
return &User{ loader: YamlLoadDecl }
|
return &User{loader: YamlLoadDecl}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ResourceTypes.Register("user", func(u *url.URL) Resource {
|
ResourceTypes.Register("user", func(u *url.URL) Resource {
|
||||||
user := NewUser()
|
user := NewUser()
|
||||||
user.Name = u.Path
|
user.Name = u.Path
|
||||||
user.UID, _ = LookupUID(u.Path)
|
user.UID, _ = LookupUID(u.Path)
|
||||||
return user
|
return user
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) URI() string {
|
func (u *User) URI() string {
|
||||||
return fmt.Sprintf("user://%s", u.Name)
|
return fmt.Sprintf("user://%s", u.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) ResolveId(ctx context.Context) string {
|
func (u *User) ResolveId(ctx context.Context) string {
|
||||||
return LookupUIDString(u.Name)
|
return LookupUIDString(u.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) Apply() error {
|
func (u *User) Apply() error {
|
||||||
switch u.State {
|
switch u.State {
|
||||||
case "present":
|
case "present":
|
||||||
_, NoUserExists := LookupUID(u.Name)
|
_, NoUserExists := LookupUID(u.Name)
|
||||||
if NoUserExists != nil {
|
if NoUserExists != nil {
|
||||||
var userCommandName string = "useradd"
|
var userCommandName string = "useradd"
|
||||||
args := make([]string, 0, 7)
|
args := make([]string, 0, 7)
|
||||||
if u.UID >= 0 {
|
if u.UID >= 0 {
|
||||||
args = append(args, "-u", fmt.Sprintf("%d", u.UID))
|
args = append(args, "-u", fmt.Sprintf("%d", u.UID))
|
||||||
}
|
}
|
||||||
|
|
||||||
if _,pathErr := exec.LookPath("useradd"); pathErr != nil {
|
if _, pathErr := exec.LookPath("useradd"); pathErr != nil {
|
||||||
if _,addUserPathErr := exec.LookPath("adduser"); addUserPathErr == nil {
|
if _, addUserPathErr := exec.LookPath("adduser"); addUserPathErr == nil {
|
||||||
userCommandName = "adduser"
|
userCommandName = "adduser"
|
||||||
u.AddUserCommand(&args)
|
u.AddUserCommand(&args)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
u.UserAddCommand(&args)
|
u.UserAddCommand(&args)
|
||||||
}
|
}
|
||||||
args = append(args, u.Name)
|
args = append(args, u.Name)
|
||||||
cmd := exec.Command(userCommandName, args...)
|
cmd := exec.Command(userCommandName, args...)
|
||||||
cmdOutput, cmdErr := cmd.CombinedOutput()
|
cmdOutput, cmdErr := cmd.CombinedOutput()
|
||||||
log.Printf("%s\n", cmdOutput)
|
log.Printf("%s\n", cmdOutput)
|
||||||
return cmdErr
|
return cmdErr
|
||||||
}
|
}
|
||||||
case "absent":
|
case "absent":
|
||||||
var userDelCommandName string = "userdel"
|
var userDelCommandName string = "userdel"
|
||||||
args := make([]string, 0, 7)
|
args := make([]string, 0, 7)
|
||||||
|
|
||||||
if _,pathErr := exec.LookPath("userdel"); pathErr != nil {
|
if _, pathErr := exec.LookPath("userdel"); pathErr != nil {
|
||||||
if _,delUserPathErr := exec.LookPath("deluser"); delUserPathErr == nil {
|
if _, delUserPathErr := exec.LookPath("deluser"); delUserPathErr == nil {
|
||||||
userDelCommandName = "deluser"
|
userDelCommandName = "deluser"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
args = append(args, u.Name)
|
args = append(args, u.Name)
|
||||||
cmd := exec.Command(userDelCommandName, args...)
|
cmd := exec.Command(userDelCommandName, args...)
|
||||||
cmdOutput, cmdErr := cmd.CombinedOutput()
|
cmdOutput, cmdErr := cmd.CombinedOutput()
|
||||||
log.Printf("%s\n", cmdOutput)
|
log.Printf("%s\n", cmdOutput)
|
||||||
return cmdErr
|
return cmdErr
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) LoadDecl(yamlFileResourceDeclaration string) error {
|
func (u *User) LoadDecl(yamlFileResourceDeclaration string) error {
|
||||||
return u.loader(yamlFileResourceDeclaration, u)
|
return u.loader(yamlFileResourceDeclaration, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) AddUserCommand(args *[]string) error {
|
func (u *User) AddUserCommand(args *[]string) error {
|
||||||
*args = append(*args, "-D")
|
*args = append(*args, "-D")
|
||||||
if u.Group != "" {
|
if u.Group != "" {
|
||||||
*args = append(*args, "-G", u.Group)
|
*args = append(*args, "-G", u.Group)
|
||||||
}
|
}
|
||||||
if u.Home != "" {
|
if u.Home != "" {
|
||||||
*args = append(*args, "-h", u.Home)
|
*args = append(*args, "-h", u.Home)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) UserAddCommand(args *[]string) error {
|
func (u *User) UserAddCommand(args *[]string) error {
|
||||||
if u.Group != "" {
|
if u.Group != "" {
|
||||||
*args = append(*args, "-g", u.Group)
|
*args = append(*args, "-g", u.Group)
|
||||||
}
|
}
|
||||||
if len(u.Groups) > 0 {
|
if len(u.Groups) > 0 {
|
||||||
*args = append(*args, "-G", strings.Join(u.Groups, ","))
|
*args = append(*args, "-G", strings.Join(u.Groups, ","))
|
||||||
}
|
}
|
||||||
if u.Home != "" {
|
if u.Home != "" {
|
||||||
*args = append(*args, "-d", u.Home)
|
*args = append(*args, "-d", u.Home)
|
||||||
}
|
}
|
||||||
if u.CreateHome {
|
if u.CreateHome {
|
||||||
*args = append(*args, "-m")
|
*args = append(*args, "-m")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) Type() string { return "user" }
|
func (u *User) Type() string { return "user" }
|
||||||
|
|
||||||
func (u *User) Read(ctx context.Context) ([]byte, error) {
|
func (u *User) Read(ctx context.Context) ([]byte, error) {
|
||||||
var readUser *user.User
|
var readUser *user.User
|
||||||
var e error
|
var e error
|
||||||
if u.Name != "" {
|
if u.Name != "" {
|
||||||
readUser,e = user.Lookup(u.Name)
|
readUser, e = user.Lookup(u.Name)
|
||||||
}
|
}
|
||||||
if u.UID >= 0 {
|
if u.UID >= 0 {
|
||||||
readUser,e = user.LookupId(strconv.Itoa(u.UID))
|
readUser, e = user.LookupId(strconv.Itoa(u.UID))
|
||||||
}
|
}
|
||||||
|
|
||||||
if e != nil {
|
if e != nil {
|
||||||
panic(e)
|
panic(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.Name = readUser.Username
|
u.Name = readUser.Username
|
||||||
u.UID,_ = strconv.Atoi(readUser.Uid)
|
u.UID, _ = strconv.Atoi(readUser.Uid)
|
||||||
if readGroup, groupErr := user.LookupGroupId(readUser.Gid); groupErr == nil {
|
if readGroup, groupErr := user.LookupGroupId(readUser.Gid); groupErr == nil {
|
||||||
u.Group = readGroup.Name
|
u.Group = readGroup.Name
|
||||||
} else {
|
} else {
|
||||||
panic(groupErr)
|
panic(groupErr)
|
||||||
}
|
}
|
||||||
u.Home = readUser.HomeDir
|
u.Home = readUser.HomeDir
|
||||||
u.Gecos = readUser.Name
|
u.Gecos = readUser.Name
|
||||||
|
|
||||||
return yaml.Marshal(u)
|
return yaml.Marshal(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,45 +2,45 @@
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "fmt"
|
_ "context"
|
||||||
_ "context"
|
_ "encoding/json"
|
||||||
"testing"
|
_ "fmt"
|
||||||
_ "net/http"
|
"github.com/stretchr/testify/assert"
|
||||||
_ "net/http/httptest"
|
_ "io"
|
||||||
_ "net/url"
|
_ "net/http"
|
||||||
_ "io"
|
_ "net/http/httptest"
|
||||||
_ "os"
|
_ "net/url"
|
||||||
"github.com/stretchr/testify/assert"
|
_ "os"
|
||||||
_ "encoding/json"
|
_ "strings"
|
||||||
_ "strings"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewUserResource(t *testing.T) {
|
func TestNewUserResource(t *testing.T) {
|
||||||
u := NewUser()
|
u := NewUser()
|
||||||
assert.NotEqual(t, nil, u)
|
assert.NotEqual(t, nil, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateUser(t *testing.T) {
|
func TestCreateUser(t *testing.T) {
|
||||||
decl := `
|
decl := `
|
||||||
name: "testuser"
|
name: "testuser"
|
||||||
uid: 12001
|
uid: 12001
|
||||||
gid: 12001
|
gid: 12001
|
||||||
home: "/home/testuser"
|
home: "/home/testuser"
|
||||||
state: present
|
state: present
|
||||||
`
|
`
|
||||||
u := NewUser()
|
u := NewUser()
|
||||||
e := u.LoadDecl(decl)
|
e := u.LoadDecl(decl)
|
||||||
assert.Equal(t, nil, e)
|
assert.Equal(t, nil, e)
|
||||||
assert.Equal(t, "testuser", u.Name)
|
assert.Equal(t, "testuser", u.Name)
|
||||||
|
|
||||||
applyErr := u.Apply()
|
applyErr := u.Apply()
|
||||||
assert.Equal(t, nil, applyErr)
|
assert.Equal(t, nil, applyErr)
|
||||||
uid, uidErr := LookupUID(u.Name)
|
uid, uidErr := LookupUID(u.Name)
|
||||||
assert.Equal(t, nil, uidErr)
|
assert.Equal(t, nil, uidErr)
|
||||||
assert.Equal(t, 12001, uid)
|
assert.Equal(t, 12001, uid)
|
||||||
|
|
||||||
u.State = "absent"
|
u.State = "absent"
|
||||||
|
|
||||||
applyDeleteErr := u.Apply()
|
applyDeleteErr := u.Apply()
|
||||||
assert.Equal(t, nil, applyDeleteErr)
|
assert.Equal(t, nil, applyDeleteErr)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user