initial version
This commit is contained in:
parent
5083338d2c
commit
d4e934fee8
28
go.mod
Normal file
28
go.mod
Normal 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
82
go.sum
Normal 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=
|
35
internal/resource/document.go
Normal file
35
internal/resource/document.go
Normal 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
|
||||
}
|
59
internal/resource/document_test.go
Normal file
59
internal/resource/document_test.go
Normal 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
119
internal/resource/file.go
Normal 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)
|
||||
}
|
91
internal/resource/file_test.go
Normal file
91
internal/resource/file_test.go
Normal 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
34
internal/resource/os.go
Normal 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
|
||||
}
|
32
internal/resource/os_test.go
Normal file
32
internal/resource/os_test.go
Normal 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) {
|
||||
|
||||
}
|
57
internal/resource/resource.go
Normal file
57
internal/resource/resource.go
Normal 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)
|
||||
}
|
74
internal/resource/resource_test.go
Normal file
74
internal/resource/resource_test.go
Normal 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)
|
||||
}
|
30
internal/resource/types.go
Normal file
30
internal/resource/types.go
Normal 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)
|
||||
}
|
32
internal/resource/types_test.go
Normal file
32
internal/resource/types_test.go
Normal 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
102
internal/resource/user.go
Normal 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
|
||||
}
|
45
internal/resource/user_test.go
Normal file
45
internal/resource/user_test.go
Normal 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
15
tests/mocks/container.go
Normal 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
14
tests/mocks/resource.go
Normal 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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user