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