initial version

This commit is contained in:
Matthew Rich 2024-03-20 09:15:27 -07:00
parent 5083338d2c
commit d4e934fee8
16 changed files with 849 additions and 0 deletions

28
go.mod Normal file
View File

@ -0,0 +1,28 @@
module decl
go 1.21.1
require github.com/stretchr/testify v1.8.4
require (
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/docker v25.0.3+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/sys v0.1.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

82
go.sum Normal file
View File

@ -0,0 +1,82 @@
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ=
github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -0,0 +1,35 @@
package resource
import (
_ "fmt"
"io"
"gopkg.in/yaml.v3"
)
type Document struct {
Nodes []yaml.Node `yaml:"resources"`
ResourceDecls []Resource `-`
}
func NewDocument() *Document {
return &Document {}
}
func (d *Document) Load(r io.Reader) error {
yamlDecoder := yaml.NewDecoder(r)
yamlDecoder.Decode(d)
d.ResourceDecls = make([]Resource, len(d.Nodes))
for i,node := range(d.Nodes) {
resourceDecl := NewDeclaration()
node.Decode(resourceDecl)
if r,e := ResourceTypes.New(resourceDecl.Type); e == nil {
resourceDecl.Attributes.Decode(r)
d.ResourceDecls[i] = r
}
}
return nil
}
func (d *Document) Resources() []Resource {
return d.ResourceDecls
}

View File

@ -0,0 +1,59 @@
package resource
import (
"os"
"fmt"
"log"
"strings"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewDocumentLoader(t *testing.T) {
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)
file := filepath.Join(dir, "foo.txt")
document := fmt.Sprintf(`
---
resources:
- type: file
attributes:
path: "%s"
owner: "nobody"
group: "nobody"
mode: "0600"
content: |-
test line 1
test line 2
state: present
- type: user
attributes:
name: "testuser"
uid: "10022"
gid: "10022"
home: "/home/testuser"
state: present
`, file)
d := NewDocument()
assert.NotEqual(t, nil, d)
docReader := strings.NewReader(document)
e := d.Load(docReader)
assert.Equal(t, nil, e)
resources := d.Resources()
assert.Equal(t, 2, len(resources))
}

119
internal/resource/file.go Normal file
View File

@ -0,0 +1,119 @@
package resource
import (
"fmt"
"os"
"os/user"
"io"
"syscall"
"gopkg.in/yaml.v3"
"strconv"
)
func init() {
ResourceTypes.Register("file", func() Resource { return NewFile() })
}
type File struct {
loader YamlLoader
Path string `yaml:"path"`
Owner string `yaml:"owner"`
Group string `yaml:"group"`
Mode string `yaml:"mode"`
Content string `yaml:"content"`
State string `yaml:"state"`
}
func NewFile() *File {
return &File{ loader: YamlLoadDecl }
}
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
}
gid,gidErr := LookupGID(f.Group)
if gidErr != nil {
return gidErr
}
//e := os.Stat(f.path)
//if os.IsNotExist(e) {
createdFile,e := os.Create(f.Path)
if e != nil {
return e
}
defer createdFile.Close()
if chownErr := createdFile.Chown(uid, gid); chownErr != nil {
return chownErr
}
mode,modeErr := strconv.ParseInt(f.Mode, 8, 64)
if modeErr != nil {
return modeErr
}
if chmodErr := createdFile.Chmod(os.FileMode(mode)); chmodErr != nil {
return chmodErr
}
_,writeErr := createdFile.Write([]byte(f.Content))
if writeErr != nil {
return writeErr
}
}
}
return nil
}
func (f *File) LoadDecl(yamlFileResourceDeclaration string) error {
return f.loader(yamlFileResourceDeclaration, f)
}
func (f *File) Read() ([]byte, error) {
info, e := os.Stat(f.Path)
if e != nil {
panic(e)
}
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
fileUser, userErr := user.LookupId(strconv.Itoa(int(stat.Uid)))
if userErr != nil { //UnknownUserIdError
panic(userErr)
}
fileGroup, groupErr := user.LookupGroupId(strconv.Itoa(int(stat.Gid)))
if groupErr != nil {
panic(groupErr)
}
f.Owner = fileUser.Name
f.Group = fileGroup.Name
}
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)
}

View File

@ -0,0 +1,91 @@
package resource
import (
"fmt"
_ "context"
"testing"
_ "net/http"
_ "net/http/httptest"
_ "net/url"
_ "io"
"os"
_ "log"
"path/filepath"
"github.com/stretchr/testify/assert"
_ "encoding/json"
_ "strings"
)
func TestNewFileResource(t *testing.T) {
f := NewFile()
assert.NotEqual(t, nil, f)
}
func TestApplyResourceTransformation(t *testing.T) {
f := NewFile()
assert.NotEqual(t, nil, f)
//e := f.Apply()
//assert.Equal(t, nil, e)
}
func TestReadFile(t *testing.T) {
file := filepath.Join(TempDir, "fooread.txt")
decl := fmt.Sprintf(`
path: "%s"
owner: "nobody"
group: "nobody"
mode: "0600"
content: |-
test line 1
test line 2
state: present
`, file)
testFile := NewFile()
e := testFile.LoadDecl(decl)
assert.Equal(t, nil, e)
testFile.Apply()
f := NewFile()
assert.NotEqual(t, nil, f)
f.Path = file
r,e := f.Read()
assert.Equal(t, nil, e)
assert.Equal(t, "nobody", f.Owner)
assert.YAMLEq(t, decl, string(r))
}
func TestCreateFile(t *testing.T) {
file := filepath.Join(TempDir, "foo.txt")
decl := fmt.Sprintf(`
path: "%s"
owner: "nobody"
group: "nobody"
mode: "0600"
content: |-
test line 1
test line 2
state: present
`, file)
f := NewFile()
e := f.LoadDecl(decl)
assert.Equal(t, nil, e)
assert.Equal(t, "nobody", f.Owner)
applyErr := f.Apply()
assert.Equal(t, nil, applyErr)
assert.FileExists(t, file, nil)
s,e := os.Stat(file)
assert.Equal(t, nil, e)
assert.Greater(t, s.Size(), int64(0))
f.State = "absent"
assert.Equal(t, nil, f.Apply())
assert.NoFileExists(t, file, nil)
}

34
internal/resource/os.go Normal file
View File

@ -0,0 +1,34 @@
package resource
import (
"os/user"
"strconv"
)
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
}
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
}
return gid, nil
}

View File

@ -0,0 +1,32 @@
package resource
import (
_ "fmt"
_ "context"
"testing"
_ "net/http"
_ "net/http/httptest"
_ "net/url"
_ "io"
"github.com/stretchr/testify/assert"
_ "encoding/json"
_ "strings"
)
func TestLookupUID(t *testing.T) {
uid,e := LookupUID("nobody")
assert.Equal(t, nil, e)
assert.Equal(t, 65534, uid)
}
func TestLookupGID(t *testing.T) {
gid,e := LookupGID("nobody")
assert.Equal(t, nil, e)
assert.Equal(t, 65534, gid)
}
func TestExecCommand(t *testing.T) {
}

View File

@ -0,0 +1,57 @@
package resource
import (
"gopkg.in/yaml.v3"
)
type Declaration struct {
Type string `yaml:"type"`
Attributes yaml.Node `yaml:"attributes"`
}
type Resource interface {
ResourceLoader
StateTransformer
}
type ResourceLoader interface {
LoadDecl(string) error
}
type StateTransformer interface {
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
}
type ResourceCreator interface {
Create() error
}
type ResourceReader interface {
Read() ([]byte, error)
}
type ResourceUpdater interface {
Update() error
}
type ResourceDeleter interface {
Delete() error
}
func NewDeclaration() *Declaration {
return &Declaration{}
}
func (d *Declaration) LoadDecl(yamlResourceDeclaration string) error {
return YamlLoadDecl(yamlResourceDeclaration, d)
}

View File

@ -0,0 +1,74 @@
package resource
import (
"os"
"path/filepath"
"fmt"
"log"
"testing"
"github.com/stretchr/testify/assert"
)
var TempDir string
func TestMain(m *testing.M) {
var err error
TempDir, err = os.MkdirTemp("", "testresourcefile")
if err != nil || TempDir == "" {
log.Fatal(err)
}
defer os.RemoveAll(TempDir)
rc := m.Run()
os.Exit(rc)
}
func TestYamlLoadDecl(t *testing.T) {
file := filepath.Join(TempDir, "fooread.txt")
resourceAttributes := make(map[string]any)
decl := fmt.Sprintf(`
path: "%s"
owner: "nobody"
group: "nobody"
mode: "0600"
content: |-
test line 1
test line 2
`, file)
e := YamlLoadDecl(decl, &resourceAttributes)
assert.Equal(t, nil, e)
assert.Equal(t, "nobody", resourceAttributes["group"])
}
func TestNewResourceDeclaration(t *testing.T) {
resourceDeclaration := NewDeclaration()
assert.NotEqual(t, nil, resourceDeclaration)
}
func TestNewResourceDeclarationType(t *testing.T) {
file := filepath.Join(TempDir, "fooread.txt")
decl := fmt.Sprintf(`
type: file
attributes:
path: "%s"
owner: "nobody"
group: "nobody"
mode: "0600"
content: |-
test line 1
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)
}

View File

@ -0,0 +1,30 @@
package resource
import (
"fmt"
)
var (
ResourceTypes *Types = NewTypes()
)
type TypeFactory func() Resource
type Types struct {
registry map[string]TypeFactory
}
func NewTypes() *Types {
return &Types{ registry: make(map[string]TypeFactory) }
}
func (t *Types) Register(name string, factory TypeFactory) {
t.registry[name] = factory
}
func (t *Types) New(name string) (Resource, error) {
if r,ok := t.registry[name]; ok {
return r(), nil
}
return nil, fmt.Errorf("Unknown type: %s", name)
}

View File

@ -0,0 +1,32 @@
package resource
import (
"testing"
"github.com/stretchr/testify/assert"
"decl/tests/mocks"
)
func TestNewResourceTypes(t *testing.T) {
resourceTypes := NewTypes()
assert.NotEqual(t, nil, resourceTypes)
}
func TestNewResourceTypesRegister(t *testing.T) {
m := &mocks.MockResource {
InjectLoadDecl: func(string) error { return nil },
InjectApply: func() error { return nil },
}
resourceTypes := NewTypes()
assert.NotEqual(t, nil, resourceTypes)
resourceTypes.Register("foo", func() Resource { return m })
r,e := resourceTypes.New("foo")
assert.Equal(t, nil, e)
assert.Equal(t, m, r)
}
func TestResourceTypesLoadResource(t *testing.T) {
}

102
internal/resource/user.go Normal file
View File

@ -0,0 +1,102 @@
package resource
import (
"fmt"
_ "os"
_ "gopkg.in/yaml.v3"
"os/exec"
"strings"
"log"
)
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"`
}
func NewUser() *User {
return &User{ loader: YamlLoadDecl }
}
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
}
func (u *User) LoadDecl(yamlFileResourceDeclaration string) error {
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
}
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
}

View File

@ -0,0 +1,45 @@
package resource
import (
_ "fmt"
_ "context"
"testing"
_ "net/http"
_ "net/http/httptest"
_ "net/url"
_ "io"
_ "os"
"github.com/stretchr/testify/assert"
_ "encoding/json"
_ "strings"
)
func TestNewUserResource(t *testing.T) {
u := NewUser()
assert.NotEqual(t, nil, u)
}
func TestCreateUser(t *testing.T) {
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)
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"
applyDeleteErr := u.Apply()
assert.Equal(t, nil, applyDeleteErr)
}

15
tests/mocks/container.go Normal file
View File

@ -0,0 +1,15 @@
package mocks
/*
import (
"net/http/httptest"
)
func newMockClient(doer func(*http.Request) (*http.Response, error)) *http.Client {
return &http.Client{
Transport: transportEnsureBody(transportFunc(doer)),
}
}
*/

14
tests/mocks/resource.go Normal file
View File

@ -0,0 +1,14 @@
package mocks
type MockResource struct {
InjectLoadDecl func(string) error
InjectApply func() error
}
func (m *MockResource) LoadDecl(yamlResourceDeclaration string) error {
return m.InjectLoadDecl(yamlResourceDeclaration)
}
func (m *MockResource) Apply() error {
return m.InjectApply()
}