diff --git a/Makefile b/Makefile index 451cca6..d5d3397 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,12 @@ -LDFLAGS?=--ldflags '-extldflags "-static"' +LDFLAGS?=--ldflags '-extldflags "-static"' --ldflags="-X 'main.commit=$(shell git rev-parse HEAD)' -X 'main.version=$(shell git describe --tags)' -X 'main.date=$(shell date '+%Y-%m-%d %T.%s%z')'" export CGO_ENABLED=0 -build: jx +.PHONY=jx-cli -jx: +build: jx-cli + +jx-cli: go build -o jx $(LDFLAGS) ./cmd/cli/main.go -test: +test: jx-cli go test ./... diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 1b09b01..34530b4 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -14,6 +14,7 @@ _ "errors" _ "gopkg.in/yaml.v3" "decl/internal/resource" "decl/internal/source" + "decl/internal/target" ) const ( @@ -21,7 +22,14 @@ const ( FormatJson = "json" ) +var ( + version string + commit string + date string +) + var GlobalOformat *string +var GlobalOutput *string var GlobalQuiet *bool var ImportMerge *bool @@ -52,6 +60,13 @@ var jxSubCommands = []SubCommand { }, } +func VersionUsage() { + fmt.Println("jx") + fmt.Printf("version: %s\n", version) + fmt.Printf("commit: %s\n", commit) + fmt.Printf("date: %s\n", date) +} + func LoggerConfig() { var programLevel = new(slog.LevelVar) logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel})) @@ -97,7 +112,7 @@ func ImportSubCommand(cmd *flag.FlagSet, output io.Writer) (err error) { documents = append(documents, loaded...) } } - + switch *GlobalOformat { case FormatYaml: encoder = resource.NewYAMLEncoder(output) @@ -105,6 +120,14 @@ func ImportSubCommand(cmd *flag.FlagSet, output io.Writer) (err error) { encoder = resource.NewJSONEncoder(output) } + if *GlobalOutput != "" { + _, err := target.TargetTypes.New(*GlobalOutput) + if err != nil { + log.Fatal(err) + } + //outputTarget.EmitResources( + } + if len(documents) == 0 { documents = append(documents, resource.NewDocument()) } @@ -253,6 +276,8 @@ func main() { for _,subCmd := range jxSubCommands { cmdFlagSet := flag.NewFlagSet(subCmd.Name, flag.ExitOnError) GlobalOformat = cmdFlagSet.String("oformat", "yaml", "Output serialization format") + GlobalOutput = cmdFlagSet.String("output", "-", "Output target (default stdout)") + GlobalOutput = cmdFlagSet.String("o", "-", "Output target (default stdout)") GlobalQuiet = cmdFlagSet.Bool("quiet", false, "Generate terse output.") switch subCmd.Name { @@ -260,16 +285,19 @@ func main() { cmdFlagSet.Usage = func() { fmt.Println("jx diff source [source2]") cmdFlagSet.PrintDefaults() + VersionUsage() } case "apply": cmdFlagSet.Usage = func() { fmt.Println("jx diff source [source2]") cmdFlagSet.PrintDefaults() + VersionUsage() } case "import": cmdFlagSet.Usage = func() { fmt.Println("jx import source [source2]") cmdFlagSet.PrintDefaults() + VersionUsage() } } slog.Info("command", "command", subCmd) @@ -278,6 +306,10 @@ func main() { log.Fatal(e) } return + } else { + flag.PrintDefaults() + VersionUsage() + os.Exit(1) } } diff --git a/internal/resource/document.go b/internal/resource/document.go index 462e492..8b79ae0 100644 --- a/internal/resource/document.go +++ b/internal/resource/document.go @@ -21,6 +21,17 @@ func NewDocument() *Document { return &Document{} } +func (d *Document) Filter(filter ResourceSelector) []*Declaration { + resources := make([]*Declaration, 0, len(d.ResourceDecls)) + for i := range d.ResourceDecls { + filterResource := &d.ResourceDecls[i] + if filter(filterResource) { + resources = append(resources, &d.ResourceDecls[i]) + } + } + return resources +} + func (d *Document) Clone() *Document { clone := NewDocument() clone.ResourceDecls = make([]Declaration, len(d.ResourceDecls)) diff --git a/internal/resource/document_test.go b/internal/resource/document_test.go index 499c259..8a58204 100644 --- a/internal/resource/document_test.go +++ b/internal/resource/document_test.go @@ -96,6 +96,7 @@ resources: ctime: %s mtime: %s sha256: ea33e2082ca777f82dc9571b08df95d81925eed04e1bdbac7cdc6dc52d330eca + size: 82 filetype: "regular" state: present `, file, fileContent, aTime.Format(time.RFC3339Nano), cTime.Format(time.RFC3339Nano), mTime.Format(time.RFC3339Nano)) @@ -185,3 +186,39 @@ resources: assert.YAMLEq(t, string(document), string(marshalledYAML)) } + +func TestDocumentResourceFilter(t *testing.T) { + document := ` +--- +resources: +- type: user + attributes: + name: "testuser" + uid: 10022 + home: "/home/testuser" + state: present +- type: file + attributes: + path: "foo.txt" + state: present +- type: file + attributes: + path: "bar.txt" + state: present +` + + d := NewDocument() + assert.NotNil(t, d) + docReader := strings.NewReader(document) + + e := d.Load(docReader) + assert.Nil(t, e) + + resources := d.Filter(func(d *Declaration) bool { + if d.Type == "file" { + return true + } + return false + }) + assert.Equal(t, 2, len(resources)) +} diff --git a/internal/resource/file.go b/internal/resource/file.go index 066dc9f..a70bbc6 100644 --- a/internal/resource/file.go +++ b/internal/resource/file.go @@ -9,6 +9,7 @@ import ( "log/slog" "gopkg.in/yaml.v3" "io" + "io/fs" "net/url" "os" "os/user" @@ -55,15 +56,20 @@ type File struct { Content string `json:"content,omitempty" yaml:"content,omitempty"` Sha256 string `json:"sha256,omitempty" yaml:"sha256,omitempty"` + Size int64 `json:"size,omitempty" yaml:"size,omitempty"` Target string `json:"target,omitempty" yaml:"target,omitempty"` FileType FileType `json:"filetype" yaml:"filetype"` State string `json:"state" yaml:"state"` } +type ResourceFileInfo struct { + resource *File +} + func NewFile() *File { currentUser, _ := user.Current() group, _ := user.LookupGroupId(currentUser.Gid) - f := &File{Owner: currentUser.Username, Group: group.Name, Mode: "0666", FileType: RegularFile} + f := &File{Owner: currentUser.Username, Group: group.Name, Mode: "0644", FileType: RegularFile} slog.Info("NewFile()", "file", f) return f } @@ -78,6 +84,8 @@ func (f *File) Clone() Resource { Ctime: f.Ctime, Mtime: f.Mtime, Content: f.Content, + Sha256: f.Sha256, + Size: f.Size, Target: f.Target, FileType: f.FileType, State: f.State, @@ -127,10 +135,12 @@ func (f *File) Apply() error { return gidErr } + slog.Info("File.Mode", "mode", f.Mode) mode, modeErr := strconv.ParseInt(f.Mode, 8, 64) if modeErr != nil { return modeErr } + slog.Info("File.Mode Parse", "mode", mode, "err", modeErr) //e := os.Stat(f.path) //if os.IsNotExist(e) { @@ -199,6 +209,43 @@ func (f *File) NormalizePath() error { return fileAbsErr } +func (f *File) FileInfo() fs.FileInfo { + return &ResourceFileInfo{ resource: f } +} + +func (f *ResourceFileInfo) Name() string { + return filepath.Base(f.resource.Path) +} + +func (f *ResourceFileInfo) Size() int64 { + return f.resource.Size +} + +func (f *ResourceFileInfo) Mode() (mode os.FileMode) { + if fileMode, fileModeErr := strconv.ParseInt(f.resource.Mode, 8, 64); fileModeErr == nil { + mode |= os.FileMode(fileMode) + } else { + panic(fileModeErr) + } + mode |= f.resource.FileType.GetMode() + return +} + +func (f *ResourceFileInfo) ModTime() time.Time { + return f.resource.Mtime +} + +func (f *ResourceFileInfo) IsDir() bool { + if f.resource.FileType == DirectoryFile { + return true + } + return false +} + +func (f *ResourceFileInfo) Sys() any { + return nil +} + func (f *File) UpdateAttributesFromFileInfo(info os.FileInfo) error { if info != nil { f.Mtime = info.ModTime() @@ -223,6 +270,7 @@ func (f *File) UpdateAttributesFromFileInfo(info os.FileInfo) error { f.Group = fileGroup.Name } } + f.Size = info.Size() f.Mode = fmt.Sprintf("%04o", info.Mode().Perm()) f.FileType.SetMode(info.Mode()) return nil @@ -309,3 +357,22 @@ func (f *FileType) SetMode(mode os.FileMode) { *f = BlockDeviceFile } } + +func (f *FileType) GetMode() (mode os.FileMode) { + switch *f { + case RegularFile: + case DirectoryFile: + mode |= os.ModeDir + case SymbolicLinkFile: + mode |= os.ModeSymlink + case NamedPipeFile: + mode |= os.ModeNamedPipe + case SocketFile: + mode |= os.ModeSocket + case CharacterDeviceFile: + mode |= os.ModeCharDevice + case BlockDeviceFile: + mode |= os.ModeDevice + } + return +} diff --git a/internal/resource/file_test.go b/internal/resource/file_test.go index a25e04e..fe05653 100644 --- a/internal/resource/file_test.go +++ b/internal/resource/file_test.go @@ -1,4 +1,5 @@ // Copyright 2024 Matthew Rich . All rights reserved. + package resource import ( @@ -49,6 +50,7 @@ func TestReadFile(t *testing.T) { test line 1 test line 2 sha256: f2082f984f1bf1a7886e2af32ccc9ca474fbff3553d131204b070c438114dd51 + size: 23 filetype: "regular" state: present ` @@ -261,3 +263,20 @@ func TestFileReadStat(t *testing.T) { assert.Nil(t, testReadErr) assert.Equal(t, linkTargetFile, testRead.Target) } + +func TestFileResourceFileInfo(t *testing.T) { + testFile := filepath.Join(TempDir, "testuri.txt") + + f := NewFile() + assert.NotNil(t, f) + + f.Path = testFile + f.Mode = "0600" + f.State = "present" + assert.Nil(t, f.Apply()) + + f.Read(context.Background()) + + fi := f.FileInfo() + assert.Equal(t, os.FileMode(0600), fi.Mode().Perm()) +} diff --git a/internal/resource/resource.go b/internal/resource/resource.go index 8574ede..d1989e5 100644 --- a/internal/resource/resource.go +++ b/internal/resource/resource.go @@ -11,6 +11,8 @@ import ( _ "net/url" ) +type ResourceSelector func(r *Declaration) bool + type Resource interface { Type() string URI() string diff --git a/internal/resource/schema_test.go b/internal/resource/schema_test.go index 3543a40..ef61418 100644 --- a/internal/resource/schema_test.go +++ b/internal/resource/schema_test.go @@ -45,6 +45,7 @@ func TestSchemaValidateJSON(t *testing.T) { test line 1 test line 2 sha256: f2082f984f1bf1a7886e2af32ccc9ca474fbff3553d131204b070c438114dd51 + size: 23 filetype: "regular" state: present `