Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
08b2f5301f | |||
3f3278b73e | |||
9943452ff9 | |||
8feb7b8d56 | |||
b61010b99a | |||
a3c2d17c69 | |||
a73acb8b93 | |||
d88b67ea2f | |||
6925598bf2 | |||
2e84afa87f | |||
784d586f48 | |||
1117882ced | |||
37ed8bfa83 | |||
0d748fb0bf | |||
05e1539134 | |||
ea97070fea | |||
dae9c1cf45 |
2
Makefile
2
Makefile
@ -32,6 +32,8 @@ run:
|
|||||||
docker run -it -v $(HOME)/.git-credentials:/root/.git-credentials -v $(HOME)/.gitconfig:/root/.gitconfig -v /var/run/docker.sock:/var/run/docker.sock -v $(shell pwd):/src $(IMAGE) sh
|
docker run -it -v $(HOME)/.git-credentials:/root/.git-credentials -v $(HOME)/.gitconfig:/root/.gitconfig -v /var/run/docker.sock:/var/run/docker.sock -v $(shell pwd):/src $(IMAGE) sh
|
||||||
run-alpine:
|
run-alpine:
|
||||||
docker run -it -v $(HOME)/.git-credentials:/root/.git-credentials -v $(HOME)/.gitconfig:/root/.gitconfig -v /var/run/docker.sock:/var/run/docker.sock -v $(shell pwd):/src golang:1.22.6-alpine sh
|
docker run -it -v $(HOME)/.git-credentials:/root/.git-credentials -v $(HOME)/.gitconfig:/root/.gitconfig -v /var/run/docker.sock:/var/run/docker.sock -v $(shell pwd):/src golang:1.22.6-alpine sh
|
||||||
|
build-container:
|
||||||
|
docker run -it -v $(HOME)/.git-credentials:/root/.git-credentials -v $(HOME)/.gitconfig:/root/.gitconfig -v /var/run/docker.sock:/var/run/docker.sock -v $(shell pwd):/src -w /src rosskeenhouse/build-golang:1.22.6-alpine sh
|
||||||
clean:
|
clean:
|
||||||
go clean -modcache
|
go clean -modcache
|
||||||
rm jx
|
rm jx
|
||||||
|
10
README.md
10
README.md
@ -10,7 +10,9 @@ These tools work with YAML descriptions of resources (E.g. files, users, contain
|
|||||||
|
|
||||||
# Releases
|
# Releases
|
||||||
|
|
||||||
**v0 releases are unstable and changes may be made to interfaces and specifications.**
|
**<span style="color:red">v0 releases are unstable and changes may be made to interfaces and specifications.</span>**
|
||||||
|
|
||||||
|
Use at your own risk.
|
||||||
|
|
||||||
# JX Documents
|
# JX Documents
|
||||||
|
|
||||||
@ -41,14 +43,14 @@ resources:
|
|||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
|
|
||||||
Testing the current version involves checking out main and building.
|
Testing the current version involves checking out main and building inside of the alpine go build container.
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://gitea.rosskeen.house/doublejynx/jx.git
|
git clone https://gitea.rosskeen.house/doublejynx/jx.git
|
||||||
|
|
||||||
make test
|
make build-container
|
||||||
|
|
||||||
make build
|
make test
|
||||||
```
|
```
|
||||||
|
|
||||||
# Command-line
|
# Command-line
|
||||||
|
16
build/docker/container-image-build.jx.yaml
Normal file
16
build/docker/container-image-build.jx.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
imports:
|
||||||
|
- /etc/jx/dockerhub.jx.yaml
|
||||||
|
resources:
|
||||||
|
- type: container-image
|
||||||
|
config: dockerhub
|
||||||
|
transition: update
|
||||||
|
attributes:
|
||||||
|
name: rosskeenhouse/build-golang:1.22.6-alpine
|
||||||
|
push: true
|
||||||
|
dockerfile: |-
|
||||||
|
FROM golang:1.22.6-alpine
|
||||||
|
COPY . /opt/build
|
||||||
|
WORKDIR /opt/build
|
||||||
|
RUN ./jx apply ./alpine.jx.yaml
|
||||||
|
contextref: file://build/docker/golang/build
|
36
build/docker/golang/build/alpine.jx.yaml
Normal file
36
build/docker/golang/build/alpine.jx.yaml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
imports:
|
||||||
|
- file://common.jx.yaml
|
||||||
|
resources:
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: musl-dev
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: luajit
|
||||||
|
verion: =~2.2
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: luajit-dev
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: protobuf
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: openjdk8
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: docker
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: openssh-client
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: golangci-lint
|
45
build/docker/golang/build/common.jx.yaml
Normal file
45
build/docker/golang/build/common.jx.yaml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
resources:
|
||||||
|
- type: file
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
path: /usr/local/bin/antlr-4.10-complete.jar
|
||||||
|
sourceref: https://www.antlr.org/download/antlr-4.10-complete.jar
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: 0755
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: make
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: openssl
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: curl
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: git
|
||||||
|
- type: package
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
name: gcc
|
||||||
|
- type: exec
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
create:
|
||||||
|
path: go
|
||||||
|
args:
|
||||||
|
- install
|
||||||
|
- google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||||
|
- type: exec
|
||||||
|
transition: create
|
||||||
|
attributes:
|
||||||
|
create:
|
||||||
|
path: go
|
||||||
|
args:
|
||||||
|
- install
|
||||||
|
- golang.org/x/vuln/cmd/govulncheck@latest
|
23
cli_test.go
23
cli_test.go
@ -184,6 +184,25 @@ func TestResourcesRead(t *testing.T) {
|
|||||||
t.Skip("cli not built")
|
t.Skip("cli not built")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
defer req.Body.Close()
|
||||||
|
assert.Equal(t, req.URL.String(), "/resource/user")
|
||||||
|
_, err := io.ReadAll(req.Body)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
userdecl := []byte(`
|
||||||
|
type: "user"
|
||||||
|
attributes:
|
||||||
|
name: "foo"
|
||||||
|
gecos: "foo user"
|
||||||
|
`)
|
||||||
|
|
||||||
|
_, writeErr := rw.Write(userdecl)
|
||||||
|
assert.Nil(t, writeErr)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
|
||||||
assert.Nil(t, TempDir.CreateFile("testread", "data"))
|
assert.Nil(t, TempDir.CreateFile("testread", "data"))
|
||||||
|
|
||||||
resources := fmt.Sprintf(`
|
resources := fmt.Sprintf(`
|
||||||
@ -215,7 +234,7 @@ resources:
|
|||||||
- type: http
|
- type: http
|
||||||
transition: read
|
transition: read
|
||||||
attributes:
|
attributes:
|
||||||
endpoint: https://gitea.rosskeen.house
|
endpoint: %s/resource/user
|
||||||
- type: route
|
- type: route
|
||||||
transition: read
|
transition: read
|
||||||
attributes:
|
attributes:
|
||||||
@ -227,7 +246,7 @@ resources:
|
|||||||
rtid: all
|
rtid: all
|
||||||
routetype: local
|
routetype: local
|
||||||
metric: 100
|
metric: 100
|
||||||
`, TempDir.FilePath("testread"))
|
`, TempDir.FilePath("testread"), server.URL)
|
||||||
|
|
||||||
assert.Nil(t, TempDir.CreateFile("resources.jx.yaml", resources))
|
assert.Nil(t, TempDir.CreateFile("resources.jx.yaml", resources))
|
||||||
|
|
||||||
|
@ -10,10 +10,10 @@ _ "decl/internal/config"
|
|||||||
_ "decl/internal/resource"
|
_ "decl/internal/resource"
|
||||||
_ "decl/internal/fan"
|
_ "decl/internal/fan"
|
||||||
"decl/internal/builtin"
|
"decl/internal/builtin"
|
||||||
_ "errors"
|
_ "errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "gopkg.in/yaml.v3"
|
_ "gopkg.in/yaml.v3"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
@ -159,7 +159,7 @@ func main() {
|
|||||||
|
|
||||||
DefaultConfigurations, configErr := builtin.BuiltInDocuments()
|
DefaultConfigurations, configErr := builtin.BuiltInDocuments()
|
||||||
if configErr != nil {
|
if configErr != nil {
|
||||||
slog.Error("Failed loading default configuration", "error", configErr)
|
slog.Warn("Failed loading default configuration", "error", configErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigDoc.AppendConfigurations(DefaultConfigurations)
|
ConfigDoc.AppendConfigurations(DefaultConfigurations)
|
||||||
@ -167,8 +167,8 @@ func main() {
|
|||||||
for _, subCmd := range jxSubCommands {
|
for _, subCmd := range jxSubCommands {
|
||||||
cmdFlagSet := flag.NewFlagSet(subCmd.Name, flag.ExitOnError)
|
cmdFlagSet := flag.NewFlagSet(subCmd.Name, flag.ExitOnError)
|
||||||
|
|
||||||
cmdFlagSet.StringVar(&ConfigPath, "config", "/etc/jx", "Config file path")
|
cmdFlagSet.StringVar(&ConfigPath, "config", "/etc/jx/conf.d", "Config file path")
|
||||||
cmdFlagSet.StringVar(&ConfigPath, "c", "/etc/jx", "Config file path")
|
cmdFlagSet.StringVar(&ConfigPath, "c", "/etc/jx/conf.d", "Config file path")
|
||||||
|
|
||||||
GlobalOformat = cmdFlagSet.String("oformat", "yaml", "Output serialization format")
|
GlobalOformat = cmdFlagSet.String("oformat", "yaml", "Output serialization format")
|
||||||
cmdFlagSet.StringVar(&GlobalOutput, "output", "-", "Output target (default stdout)")
|
cmdFlagSet.StringVar(&GlobalOutput, "output", "-", "Output target (default stdout)")
|
||||||
|
26
cmd/cli/main_test.go
Normal file
26
cmd/cli/main_test.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
_ "decl/internal/folio"
|
||||||
|
_ "decl/internal/data"
|
||||||
|
_ "log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadSourceURIConverter(t *testing.T) {
|
||||||
|
/*
|
||||||
|
var uri folio.URI = "file://../../examples/file.jx.yaml"
|
||||||
|
docs, err := LoadSourceURIConverter(uri)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Greater(t, len(docs), 0)
|
||||||
|
slog.Info("TestLoadSourceURIConverter", "doc", docs[0], "resource", docs[0].(*folio.Document).ResourceDeclarations[0].Attributes)
|
||||||
|
resDecl := docs[0].(*folio.Document).ResourceDeclarations[0]
|
||||||
|
assert.Equal(t, "file", resDecl.Attributes.Type())
|
||||||
|
v, ok := docs[0].Get("file:///tmp/foo.txt")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "/tmp/foo.txt", v.(data.Declaration).Resource().(data.FileResource).FilePath())
|
||||||
|
*/
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
resources:
|
resources:
|
||||||
- type: file
|
- type: file
|
||||||
|
transition: create
|
||||||
attributes:
|
attributes:
|
||||||
path: /tmp/foo.txt
|
path: /tmp/foo.txt
|
||||||
owner: nobody
|
owner: nobody
|
||||||
group: nobody
|
|
||||||
mode: 0644
|
mode: 0644
|
||||||
state: present
|
state: present
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
resources:
|
resources:
|
||||||
- type: user
|
- type: user
|
||||||
|
transition: create
|
||||||
attributes:
|
attributes:
|
||||||
name: "testuser"
|
name: "testuser"
|
||||||
uid: "12001"
|
uid: "12001"
|
||||||
home: "/home/testuser"
|
home: "/home/testuser"
|
||||||
state: present
|
|
||||||
|
54
internal/builtin/documents/config.jx.yaml
Normal file
54
internal/builtin/documents/config.jx.yaml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
configurations:
|
||||||
|
- name: confdir
|
||||||
|
values:
|
||||||
|
prefix: /etc/jx
|
||||||
|
resources:
|
||||||
|
- type: group
|
||||||
|
transition: create
|
||||||
|
onerror: stop
|
||||||
|
attributes:
|
||||||
|
name: "jx"
|
||||||
|
- type: file
|
||||||
|
transition: update
|
||||||
|
attributes:
|
||||||
|
path: "/etc/jx"
|
||||||
|
owner: "root"
|
||||||
|
group: "root"
|
||||||
|
mode: "0755"
|
||||||
|
filetype: directory
|
||||||
|
- type: file
|
||||||
|
transition: update
|
||||||
|
config: confdir
|
||||||
|
attributes:
|
||||||
|
path: "conf.d"
|
||||||
|
owner: "root"
|
||||||
|
group: "jx"
|
||||||
|
mode: "0770"
|
||||||
|
filetype: directory
|
||||||
|
- type: file
|
||||||
|
transition: update
|
||||||
|
config: confdir
|
||||||
|
attributes:
|
||||||
|
path: "lib"
|
||||||
|
owner: "root"
|
||||||
|
group: "jx"
|
||||||
|
mode: "0770"
|
||||||
|
filetype: directory
|
||||||
|
- type: file
|
||||||
|
transition: update
|
||||||
|
config: confdir
|
||||||
|
attributes:
|
||||||
|
path: "pki"
|
||||||
|
owner: "root"
|
||||||
|
group: "jx"
|
||||||
|
mode: "0770"
|
||||||
|
filetype: directory
|
||||||
|
- type: file
|
||||||
|
transition: update
|
||||||
|
config: confdir
|
||||||
|
attributes:
|
||||||
|
path: "pki/ca"
|
||||||
|
owner: "root"
|
||||||
|
group: "jx"
|
||||||
|
mode: "0770"
|
||||||
|
filetype: directory
|
16
internal/builtin/documents/install.jx.yaml
Normal file
16
internal/builtin/documents/install.jx.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
imports:
|
||||||
|
- file://documents/config.jx.yaml
|
||||||
|
configurations:
|
||||||
|
- name: bindir
|
||||||
|
values:
|
||||||
|
prefix: /usr/local/bin
|
||||||
|
resources:
|
||||||
|
- type: file
|
||||||
|
transition: update
|
||||||
|
config: bindir
|
||||||
|
attributes:
|
||||||
|
path: "jx"
|
||||||
|
owner: "root"
|
||||||
|
group: "root"
|
||||||
|
mode: "0755"
|
||||||
|
sourceref: file://jx
|
@ -126,7 +126,7 @@ func (a *App) ImportResource(ctx context.Context, uri string) (err error) {
|
|||||||
a.Documents = append(a.Documents, folio.DocumentRegistry.NewDocument(""))
|
a.Documents = append(a.Documents, folio.DocumentRegistry.NewDocument(""))
|
||||||
}
|
}
|
||||||
resourceURI := folio.URI(uri)
|
resourceURI := folio.URI(uri)
|
||||||
u := resourceURI.Parse()
|
u := resourceURI.Parse().URL()
|
||||||
if u == nil {
|
if u == nil {
|
||||||
return fmt.Errorf("Failed adding resource: %s", uri)
|
return fmt.Errorf("Failed adding resource: %s", uri)
|
||||||
}
|
}
|
||||||
@ -147,9 +147,19 @@ func (a *App) ImportResource(ctx context.Context, uri string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) ImportSource(uri string) (loadedDocuments []data.Document, err error) {
|
func (a *App) ImportSource(uri string) (loadedDocuments []data.Document, err error) {
|
||||||
if loadedDocuments, err = folio.DocumentRegistry.Load(folio.URI(uri)); err == nil && loadedDocuments != nil {
|
if source := folio.URI(uri).Parse().URL(); source != nil {
|
||||||
a.Documents = append(a.Documents, loadedDocuments...)
|
if source.Scheme == "" {
|
||||||
|
source.Scheme = "file"
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Client.ImportSource()", "uri", uri, "source", source, "error", err)
|
||||||
|
if loadedDocuments, err = folio.DocumentRegistry.LoadFromParsedURI(source); err == nil && loadedDocuments != nil {
|
||||||
|
a.Documents = append(a.Documents, loadedDocuments...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = folio.ErrInvalidURI
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Client.ImportSource()", "uri", uri, "error", err)
|
slog.Info("Client.ImportSource()", "uri", uri, "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -183,7 +193,7 @@ func (a *App) Apply(ctx context.Context, deleteResources bool) (err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Client.Apply()", "document", d, "state", overrideState, "error", err)
|
slog.Info("Client.Apply()", "uri", d.GetURI(), "document", d, "state", overrideState, "error", err)
|
||||||
if e := d.(*folio.Document).Apply(overrideState); e != nil {
|
if e := d.(*folio.Document).Apply(overrideState); e != nil {
|
||||||
slog.Info("Client.Apply() error", "error", e)
|
slog.Info("Client.Apply() error", "error", e)
|
||||||
return e
|
return e
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUnknownCommand error = errors.New("Unable to find command in path")
|
var ErrUnknownCommand error = errors.New("Unable to find command in path")
|
||||||
@ -26,17 +27,23 @@ type CommandExists func() error
|
|||||||
|
|
||||||
type CommandArg string
|
type CommandArg string
|
||||||
|
|
||||||
|
type CommandInput string
|
||||||
|
|
||||||
type Command struct {
|
type Command struct {
|
||||||
Path string `json:"path" yaml:"path"`
|
Path string `json:"path" yaml:"path"`
|
||||||
Args []CommandArg `json:"args" yaml:"args"`
|
Args []CommandArg `json:"args" yaml:"args"`
|
||||||
Env []string `json:"env" yaml:"env"`
|
Env []string `json:"env" yaml:"env"`
|
||||||
Split bool `json:"split" yaml:"split"`
|
Split bool `json:"split" yaml:"split"`
|
||||||
FailOnError bool `json:"failonerror" yaml:"failonerror"`
|
FailOnError bool `json:"failonerror" yaml:"failonerror"`
|
||||||
StdinAvailable bool `json:"stdinavailable,omitempty" yaml:"stdinavailable,omitempty"`
|
StdinAvailable bool `json:"stdinavailable,omitempty" yaml:"stdinavailable,omitempty"`
|
||||||
Executor CommandExecutor `json:"-" yaml:"-"`
|
ExitCode int `json:"exitcode,omitempty" yaml:"exitcode,omitempty"`
|
||||||
Extractor CommandExtractAttributes `json:"-" yaml:"-"`
|
Stdout string `json:"stdout,omitempty" yaml:"stdout,omitempty"`
|
||||||
CommandExists CommandExists `json:"-" yaml:"-"`
|
Stderr string `json:"stderr,omitempty" yaml:"stderr,omitempty"`
|
||||||
stdin io.Reader `json:"-" yaml:"-"`
|
Executor CommandExecutor `json:"-" yaml:"-"`
|
||||||
|
Extractor CommandExtractAttributes `json:"-" yaml:"-"`
|
||||||
|
CommandExists CommandExists `json:"-" yaml:"-"`
|
||||||
|
Input CommandInput `json:"-" yaml:"-"`
|
||||||
|
stdin io.Reader `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommand() *Command {
|
func NewCommand() *Command {
|
||||||
@ -45,7 +52,14 @@ func NewCommand() *Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Command) ClearOutput() {
|
||||||
|
c.Stdout = ""
|
||||||
|
c.Stderr = ""
|
||||||
|
c.ExitCode = 0
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Command) Defaults() {
|
func (c *Command) Defaults() {
|
||||||
|
c.ClearOutput()
|
||||||
c.Split = true
|
c.Split = true
|
||||||
c.FailOnError = true
|
c.FailOnError = true
|
||||||
c.CommandExists = func() error {
|
c.CommandExists = func() error {
|
||||||
@ -55,10 +69,14 @@ func (c *Command) Defaults() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
c.Executor = func(value any) ([]byte, error) {
|
c.Executor = func(value any) ([]byte, error) {
|
||||||
|
c.ClearOutput()
|
||||||
args, err := c.Template(value)
|
args, err := c.Template(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if inputErr := c.SetInput(value); inputErr != nil {
|
||||||
|
return nil, inputErr
|
||||||
|
}
|
||||||
cmd := exec.Command(c.Path, args...)
|
cmd := exec.Command(c.Path, args...)
|
||||||
c.SetCmdEnv(cmd)
|
c.SetCmdEnv(cmd)
|
||||||
|
|
||||||
@ -91,6 +109,10 @@ func (c *Command) Defaults() {
|
|||||||
}
|
}
|
||||||
waitErr := cmd.Wait()
|
waitErr := cmd.Wait()
|
||||||
|
|
||||||
|
c.Stdout = string(stdOutOutput)
|
||||||
|
c.Stderr = string(stdErrOutput)
|
||||||
|
c.ExitCode = c.GetExitCodeFromError(waitErr)
|
||||||
|
|
||||||
if len(stdOutOutput) > 100 {
|
if len(stdOutOutput) > 100 {
|
||||||
slog.Info("execute()", "path", c.Path, "args", args, "output", string(stdOutOutput[:100]), "error", string(stdErrOutput))
|
slog.Info("execute()", "path", c.Path, "args", args, "output", string(stdOutOutput[:100]), "error", string(stdErrOutput))
|
||||||
} else {
|
} else {
|
||||||
@ -126,6 +148,15 @@ func (c *Command) Exists() bool {
|
|||||||
return c.CommandExists() == nil
|
return c.CommandExists() == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Command) GetExitCodeFromError(err error) (ec int) {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||||
|
return status.ExitStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Command) Template(value any) ([]string, error) {
|
func (c *Command) Template(value any) ([]string, error) {
|
||||||
var args []string = make([]string, 0, len(c.Args) * 2)
|
var args []string = make([]string, 0, len(c.Args) * 2)
|
||||||
for i, arg := range c.Args {
|
for i, arg := range c.Args {
|
||||||
@ -154,6 +185,22 @@ func (c *Command) Execute(value any) ([]byte, error) {
|
|||||||
return c.Executor(value)
|
return c.Executor(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Command) SetInput(value any) error {
|
||||||
|
if len(c.Input) > 0 {
|
||||||
|
if r, err := c.Input.Template(value); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
c.SetStdinReader(strings.NewReader(r.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandInput) Template(value any) (result strings.Builder, err error) {
|
||||||
|
err = template.Must(template.New("commandInput").Parse(string(*c))).Execute(&result, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CommandArg) UnmarshalValue(value string) error {
|
func (c *CommandArg) UnmarshalValue(value string) error {
|
||||||
*c = CommandArg(value)
|
*c = CommandArg(value)
|
||||||
return nil
|
return nil
|
||||||
|
@ -81,3 +81,22 @@ stdinavailable: true
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, expected, string(out))
|
assert.Equal(t, expected, string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommandExitCode(t *testing.T) {
|
||||||
|
c := NewCommand()
|
||||||
|
assert.NotNil(t, c)
|
||||||
|
decl := `
|
||||||
|
path: ls
|
||||||
|
args:
|
||||||
|
- "amissingfile"
|
||||||
|
`
|
||||||
|
|
||||||
|
assert.Nil(t, c.LoadDecl(decl))
|
||||||
|
assert.Equal(t, "ls", c.Path)
|
||||||
|
|
||||||
|
out, err := c.Execute(nil)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Greater(t, c.ExitCode, 0)
|
||||||
|
assert.Equal(t, string(out), c.Stdout)
|
||||||
|
assert.Equal(t, string("ls: amissingfile: No such file or directory\n"), c.Stderr)
|
||||||
|
}
|
||||||
|
@ -37,7 +37,7 @@ func (c *Certificate) SetURI(uri string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Certificate) SetParsedURI(uri *url.URL) error {
|
func (c *Certificate) SetParsedURI(uri data.URIParser) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ func (x *Exec) SetURI(uri string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) SetParsedURI(uri *url.URL) error {
|
func (x *Exec) SetParsedURI(uri data.URIParser) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ func (g *Generic[Value]) SetURI(uri string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Generic[Value]) SetParsedURI(uri *url.URL) error {
|
func (g *Generic[Value]) SetParsedURI(uri data.URIParser) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/folio"
|
"decl/internal/folio"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"decl/internal/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Collects facts about the system
|
// Collects facts about the system
|
||||||
@ -35,9 +36,24 @@ func NewSystem() *System {
|
|||||||
for k, v := range buildValues {
|
for k, v := range buildValues {
|
||||||
s[k] = v
|
s[k] = v
|
||||||
}
|
}
|
||||||
|
s.CurrentUser()
|
||||||
|
s["importpath"] = []string {
|
||||||
|
"/etc/jx/lib",
|
||||||
|
}
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *System) CurrentUser() {
|
||||||
|
processUser := system.ProcessUser()
|
||||||
|
processGroup := system.ProcessGroup(processUser)
|
||||||
|
(*s)["user"] = processUser.Username
|
||||||
|
(*s)["gecos"] = processUser.Name
|
||||||
|
(*s)["home"] = processUser.HomeDir
|
||||||
|
(*s)["uid"] = processUser.Uid
|
||||||
|
(*s)["group"] = processGroup.Name
|
||||||
|
(*s)["gid"] = processUser.Gid
|
||||||
|
}
|
||||||
|
|
||||||
func (s *System) URI() string {
|
func (s *System) URI() string {
|
||||||
return fmt.Sprintf("%s://%s", s.Type(), "")
|
return fmt.Sprintf("%s://%s", s.Type(), "")
|
||||||
}
|
}
|
||||||
@ -46,7 +62,7 @@ func (s *System) SetURI(uri string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *System) SetParsedURI(uri *url.URL) error {
|
func (s *System) SetParsedURI(uri data.URIParser) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"io"
|
"io"
|
||||||
"decl/internal/mapper"
|
"decl/internal/mapper"
|
||||||
"net/url"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -44,7 +43,7 @@ type Document interface {
|
|||||||
mapper.Mapper
|
mapper.Mapper
|
||||||
|
|
||||||
NewResource(uri string) (Resource, error)
|
NewResource(uri string) (Resource, error)
|
||||||
NewResourceFromParsedURI(uri *url.URL) (Resource, error)
|
NewResourceFromParsedURI(uri URIParser) (Resource, error)
|
||||||
AddDeclaration(Declaration)
|
AddDeclaration(Declaration)
|
||||||
AddResourceDeclaration(resourceType string, resourceDeclaration Resource)
|
AddResourceDeclaration(resourceType string, resourceDeclaration Resource)
|
||||||
|
|
||||||
|
@ -5,16 +5,34 @@ package data
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"decl/internal/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidURI error = errors.New("Invalid URI")
|
ErrInvalidURI error = errors.New("Invalid URI")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type URIParser interface {
|
||||||
|
URL() *url.URL
|
||||||
|
NewResource(document Document) (newResource Resource, err error)
|
||||||
|
ConstructResource(res Resource) (err error)
|
||||||
|
Converter() (converter Converter, err error)
|
||||||
|
Exists() bool
|
||||||
|
|
||||||
|
ContentReaderStream() (*transport.Reader, error)
|
||||||
|
ContentWriterStream() (*transport.Writer, error)
|
||||||
|
|
||||||
|
String() string
|
||||||
|
SetURL(url *url.URL)
|
||||||
|
Extension() (string, string)
|
||||||
|
|
||||||
|
ContentType() string
|
||||||
|
IsEmpty() bool
|
||||||
|
}
|
||||||
|
|
||||||
type Identifier interface {
|
type Identifier interface {
|
||||||
URI() string
|
URI() string
|
||||||
SetURI(string) error
|
SetParsedURI(URIParser) error
|
||||||
SetParsedURI(*url.URL) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DocumentElement interface {
|
type DocumentElement interface {
|
||||||
|
@ -22,6 +22,11 @@ type StateTransformer interface {
|
|||||||
Apply() error
|
Apply() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used by the resource factory to initialize new resources.
|
||||||
|
type ResourceInitializer interface {
|
||||||
|
Init(uri URIParser) error
|
||||||
|
}
|
||||||
|
|
||||||
type Resource interface {
|
type Resource interface {
|
||||||
Identifier
|
Identifier
|
||||||
Type() string
|
Type() string
|
||||||
|
@ -11,6 +11,7 @@ type Factory[Product comparable] func(*url.URL) Product
|
|||||||
type TypesRegistry[Product comparable] interface {
|
type TypesRegistry[Product comparable] interface {
|
||||||
New(uri string) (result Product, err error)
|
New(uri string) (result Product, err error)
|
||||||
NewFromParsedURI(uri *url.URL) (result Product, err error)
|
NewFromParsedURI(uri *url.URL) (result Product, err error)
|
||||||
|
NewFromType(typename string) (result Product, err error)
|
||||||
Has(typename string) bool
|
Has(typename string) bool
|
||||||
//Get(string) Factory[Product]
|
//Get(string) Factory[Product]
|
||||||
}
|
}
|
||||||
|
63
internal/ds/orderedset.go
Normal file
63
internal/ds/orderedset.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package ds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderedSet[Value comparable] struct {
|
||||||
|
Values []*Value
|
||||||
|
elements map[Value]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOrderedSet[Value comparable]() *OrderedSet[Value] {
|
||||||
|
return &OrderedSet[Value]{ elements: make(map[Value]int), Values: make([]*Value, 0, 10) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderedSet[Value]) Add(value Value) {
|
||||||
|
slog.Info("OrderedSet.Add", "key", value, "s", s)
|
||||||
|
s.Values = append(s.Values, &value)
|
||||||
|
s.elements[value] = len(s.Values)
|
||||||
|
slog.Info("OrderedSet.Add", "key", value, "s", s, "v", &s.Values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderedSet[Value]) Delete(key Value) {
|
||||||
|
slog.Info("OrderedSet.Delete", "key", key, "s", s, "size", len(s.Values))
|
||||||
|
if i, ok := s.elements[key]; ok {
|
||||||
|
i--
|
||||||
|
s.Values[i] = nil
|
||||||
|
delete(s.elements, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderedSet[Value]) Contains(value Value) (result bool) {
|
||||||
|
slog.Info("OrderedSet.Contains", "key", value, "s", s, "size", len(s.Values), "v", &s.Values)
|
||||||
|
_, result = s.elements[value]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderedSet[Value]) Len() int {
|
||||||
|
return len(s.elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderedSet[Value]) AddItems(value []Value) {
|
||||||
|
for _, v := range value {
|
||||||
|
s.Add(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderedSet[Value]) Items() []*Value {
|
||||||
|
slog.Info("OrderedSet.Items - start", "s", s)
|
||||||
|
result := make([]*Value, 0, len(s.elements) - 1)
|
||||||
|
for _, v := range s.Values {
|
||||||
|
slog.Info("OrderedSet.Items", "value", v)
|
||||||
|
if v != nil {
|
||||||
|
result = append(result, v)
|
||||||
|
s.elements[*v] = len(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slog.Info("OrderedSet.Items", "s", s, "result", result)
|
||||||
|
s.Values = result
|
||||||
|
return result
|
||||||
|
}
|
51
internal/ds/orderedset_test.go
Normal file
51
internal/ds/orderedset_test.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package ds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewOrderedSet(t *testing.T) {
|
||||||
|
s := NewOrderedSet[string]()
|
||||||
|
assert.NotNil(t, s)
|
||||||
|
|
||||||
|
testValues := []string{
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
"baz",
|
||||||
|
"quuz",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _,value := range testValues {
|
||||||
|
|
||||||
|
s.Add(value)
|
||||||
|
|
||||||
|
|
||||||
|
slog.Info("TestNewOrderedSet - ADD", "item", value, "s", s)
|
||||||
|
|
||||||
|
assert.True(t, s.Contains(value))
|
||||||
|
slog.Info("TestNewOrderedSet - CONTAINS", "s", s)
|
||||||
|
|
||||||
|
for x, item := range s.Items() {
|
||||||
|
slog.Info("TestNewOrderedSet", "item", item, "s", s)
|
||||||
|
assert.Equal(t, testValues[x], *item)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
s.Delete("bar")
|
||||||
|
|
||||||
|
expectedValues := []string {
|
||||||
|
"foo",
|
||||||
|
"baz",
|
||||||
|
"quuz",
|
||||||
|
}
|
||||||
|
for x, item := range s.Items() {
|
||||||
|
assert.Equal(t, expectedValues[x], *item)
|
||||||
|
}
|
||||||
|
}
|
34
internal/ds/set.go
Normal file
34
internal/ds/set.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package ds
|
||||||
|
|
||||||
|
import (
|
||||||
|
)
|
||||||
|
|
||||||
|
type Set[Value comparable] map[Value]bool
|
||||||
|
|
||||||
|
func NewSet[Value comparable]() Set[Value] {
|
||||||
|
return make(map[Value]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[Value]) Add(value Value) {
|
||||||
|
s[value] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[Value]) Delete(value Value) {
|
||||||
|
delete(s, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[Value]) Contains(value Value) bool {
|
||||||
|
return s[value]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[Value]) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[Value]) AddSlice(value []Value) {
|
||||||
|
for _, v := range value {
|
||||||
|
s.Add(v)
|
||||||
|
}
|
||||||
|
}
|
24
internal/ds/set_test.go
Normal file
24
internal/ds/set_test.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package ds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewSet(t *testing.T) {
|
||||||
|
s := NewSet[string]()
|
||||||
|
assert.NotNil(t, s)
|
||||||
|
s["foo"] = true
|
||||||
|
assert.True(t, s.Contains("foo"))
|
||||||
|
|
||||||
|
s.Add("bar")
|
||||||
|
|
||||||
|
assert.True(t, s.Contains("bar"))
|
||||||
|
|
||||||
|
|
||||||
|
}
|
26
internal/ext/file.go
Normal file
26
internal/ext/file.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package ext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FilePath string
|
||||||
|
|
||||||
|
func (f *FilePath) Exists() bool {
|
||||||
|
_, err := os.Stat(string(*f))
|
||||||
|
return !os.IsNotExist(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FilePath) Add(relative string) {
|
||||||
|
newPath := filepath.Join(string(*f), relative)
|
||||||
|
*f = FilePath(newPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (f FilePath) Abs() FilePath {
|
||||||
|
result, _ := filepath.Abs(string(f))
|
||||||
|
return FilePath(result)
|
||||||
|
}
|
@ -105,12 +105,6 @@ func (d *Dir) Emit(document data.Document, filter data.ElementSelector) (resourc
|
|||||||
return nil, ErrEmptyDocument
|
return nil, ErrEmptyDocument
|
||||||
}
|
}
|
||||||
|
|
||||||
dirFileDeclaration := folio.NewDeclaration()
|
|
||||||
dirFileDeclaration.Type = "file"
|
|
||||||
if err = dirFileDeclaration.NewResource(nil); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parentPaths := make(map[string]int)
|
parentPaths := make(map[string]int)
|
||||||
var containingDirectoryPath string
|
var containingDirectoryPath string
|
||||||
for _,res := range document.Filter(func(d data.Declaration) bool {
|
for _,res := range document.Filter(func(d data.Declaration) bool {
|
||||||
@ -130,8 +124,12 @@ func (d *Dir) Emit(document data.Document, filter data.ElementSelector) (resourc
|
|||||||
|
|
||||||
containingDirectoryPath, _ = d.isParent(&parentPaths, parent, containingDirectoryPath)
|
containingDirectoryPath, _ = d.isParent(&parentPaths, parent, containingDirectoryPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
uri := fmt.Sprintf("file://%s", containingDirectoryPath)
|
uri := fmt.Sprintf("file://%s", containingDirectoryPath)
|
||||||
if err = dirFileDeclaration.SetURI(uri); err != nil {
|
|
||||||
|
dirFileDeclaration := folio.NewDeclaration()
|
||||||
|
dirFileDeclaration.Type = "file"
|
||||||
|
if err = dirFileDeclaration.NewResource(&uri); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,8 +120,10 @@ func (j *JxFile) setdecoder(source data.ContentIdentifier) {
|
|||||||
for _,v := range strings.Split(source.ContentType(), ".") {
|
for _,v := range strings.Split(source.ContentType(), ".") {
|
||||||
_ = j.Format.Set(v)
|
_ = j.Format.Set(v)
|
||||||
}
|
}
|
||||||
|
slog.Info("JxFile.setdecoder()", "type", source.ContentType(), "format", j.Format)
|
||||||
j.decoder = codec.NewDecoder(j.reader, j.Format)
|
j.decoder = codec.NewDecoder(j.reader, j.Format)
|
||||||
}
|
}
|
||||||
|
slog.Info("JxFile.setdecoder()", "decoder", j.decoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JxFile) Type() data.TypeName { return "jx" }
|
func (j *JxFile) Type() data.TypeName { return "jx" }
|
||||||
@ -179,6 +181,7 @@ func (j *JxFile) ExtractMany(resourceSource data.Resource, filter data.ElementSe
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
slog.Info("JxFile.ExtractMany() loading", "document", j.index)
|
||||||
}
|
}
|
||||||
slog.Info("JxFile.ExtractMany()", "jxfile", j, "error", err)
|
slog.Info("JxFile.ExtractMany()", "jxfile", j, "error", err)
|
||||||
return
|
return
|
||||||
@ -186,7 +189,7 @@ func (j *JxFile) ExtractMany(resourceSource data.Resource, filter data.ElementSe
|
|||||||
|
|
||||||
func (j *JxFile) targetResource() (target data.Resource, err error) {
|
func (j *JxFile) targetResource() (target data.Resource, err error) {
|
||||||
if j.emitResource == nil {
|
if j.emitResource == nil {
|
||||||
targetUrl := j.Uri.Parse()
|
targetUrl := j.Uri.Parse().URL()
|
||||||
targetUrl.Scheme = "file"
|
targetUrl.Scheme = "file"
|
||||||
q := targetUrl.Query()
|
q := targetUrl.Query()
|
||||||
q.Set("format", string(j.Format))
|
q.Set("format", string(j.Format))
|
||||||
|
@ -15,6 +15,32 @@ import (
|
|||||||
"decl/internal/schema"
|
"decl/internal/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ConfigKey string
|
||||||
|
|
||||||
|
// Lookup a config value using block.key identifier (E.g. system.GOOS)
|
||||||
|
func (c ConfigKey) GetValue() (value any, err error) {
|
||||||
|
fields := strings.SplitN(string(c), ".", 2)
|
||||||
|
if configBlock, ok := DocumentRegistry.ConfigNameMap.Get(fields[0]); ok && len(fields) > 1 {
|
||||||
|
return configBlock.GetValue(fields[1])
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("%w - %s", data.ErrUnknownConfigurationType, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ConfigKey) Get() any {
|
||||||
|
if v, err := c.GetValue(); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ConfigKey) GetStringSlice() []string {
|
||||||
|
if v, err := c.GetValue(); err == nil {
|
||||||
|
return v.([]string)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type BlockType struct {
|
type BlockType struct {
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
Type TypeName `json:"type" yaml:"type"`
|
Type TypeName `json:"type" yaml:"type"`
|
||||||
@ -135,9 +161,9 @@ func (b *Block) SetURI(uri string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Block) SetParsedURI(uri *url.URL) (err error) {
|
func (b *Block) SetParsedURI(uri data.URIParser) (err error) {
|
||||||
if b.Values == nil {
|
if b.Values == nil {
|
||||||
if err = b.NewConfigurationFromParsedURI(uri); err != nil {
|
if err = b.NewConfigurationFromParsedURI(uri.URL()); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,10 @@ _ "errors"
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
_ "gitea.rosskeen.house/rosskeen.house/machine"
|
_ "gitea.rosskeen.house/rosskeen.house/machine"
|
||||||
//_ "gitea.rosskeen.house/pylon/luaruntime"
|
"gitea.rosskeen.house/pylon/luaruntime"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/schema"
|
"decl/internal/schema"
|
||||||
"net/url"
|
|
||||||
"runtime/debug"
|
|
||||||
"errors"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,6 +31,7 @@ type DeclarationType struct {
|
|||||||
OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"`
|
OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"`
|
||||||
Error string `json:"error,omitempty" yaml:"error,omitempty"`
|
Error string `json:"error,omitempty" yaml:"error,omitempty"`
|
||||||
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
||||||
|
On *Events `json:"on,omitempty" yaml:"on,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Declaration struct {
|
type Declaration struct {
|
||||||
@ -43,14 +42,15 @@ type Declaration struct {
|
|||||||
OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"`
|
OnError OnError `json:"onerror,omitempty" yaml:"onerror,omitempty"`
|
||||||
Error string `json:"error,omitempty" yaml:"error,omitempty"`
|
Error string `json:"error,omitempty" yaml:"error,omitempty"`
|
||||||
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
||||||
// runtime luaruntime.LuaRunner
|
On *Events `json:"on,omitempty" yaml:"on,omitempty"`
|
||||||
|
runtime luaruntime.LuaRunner
|
||||||
document *Document
|
document *Document
|
||||||
configBlock data.Block
|
configBlock data.Block
|
||||||
ResourceTypes data.TypesRegistry[data.Resource] `json:"-" yaml:"-"`
|
ResourceTypes data.TypesRegistry[data.Resource] `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeclaration() *Declaration {
|
func NewDeclaration() *Declaration {
|
||||||
return &Declaration{ ResourceTypes: DocumentRegistry.ResourceTypes }
|
return &Declaration{ ResourceTypes: DocumentRegistry.ResourceTypes, runtime: luaruntime.New() }
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeclarationFromDocument(document *Document) *Declaration {
|
func NewDeclarationFromDocument(document *Document) *Declaration {
|
||||||
@ -98,6 +98,7 @@ func (d *Declaration) Clone() data.Declaration {
|
|||||||
//runtime: luaruntime.New(),
|
//runtime: luaruntime.New(),
|
||||||
Config: d.Config,
|
Config: d.Config,
|
||||||
Requires: d.Requires,
|
Requires: d.Requires,
|
||||||
|
On: d.On,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,11 +138,12 @@ func (d *Declaration) Validate() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) NewResourceFromParsedURI(u *url.URL) (err error) {
|
func (d *Declaration) NewResourceFromParsedURI(u data.URIParser) (err error) {
|
||||||
if u == nil {
|
if u == nil {
|
||||||
d.Attributes, err = d.ResourceTypes.NewFromParsedURI(&url.URL{ Scheme: string(d.Type) })
|
d.Attributes, err = d.ResourceTypes.NewFromType(string(d.Type))
|
||||||
} else {
|
} else {
|
||||||
if d.Attributes, err = d.ResourceTypes.NewFromParsedURI(u); err == nil {
|
parsed := u.URL()
|
||||||
|
if d.Attributes, err = d.ResourceTypes.NewFromParsedURI(parsed); err == nil {
|
||||||
err = d.Attributes.SetParsedURI(u)
|
err = d.Attributes.SetParsedURI(u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,15 +151,17 @@ func (d *Declaration) NewResourceFromParsedURI(u *url.URL) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) NewResource(uri *string) (err error) {
|
func (d *Declaration) NewResource(uri *string) (err error) {
|
||||||
|
slog.Info("Declaration.NewResource()")
|
||||||
if d.ResourceTypes == nil {
|
if d.ResourceTypes == nil {
|
||||||
panic(fmt.Errorf("Undefined type registry: unable to create new resource %s", *uri))
|
panic(fmt.Errorf("Undefined type registry: unable to create new resource %s", *uri))
|
||||||
}
|
}
|
||||||
|
|
||||||
if uri == nil {
|
if uri == nil {
|
||||||
d.Attributes, err = d.ResourceTypes.New(fmt.Sprintf("%s://", d.Type))
|
d.Attributes, err = d.ResourceTypes.NewFromType(string(d.Type))
|
||||||
} else {
|
} else {
|
||||||
if d.Attributes, err = d.ResourceTypes.New(*uri); err == nil {
|
slog.Info("Declaration.NewResource()", "uri", *uri)
|
||||||
err = d.Attributes.SetURI(*uri)
|
parsedURI := URI(*uri).Parse()
|
||||||
}
|
d.Attributes, err = d.ResourceTypes.NewFromParsedURI(parsedURI.URL())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -169,7 +173,6 @@ func (d *Declaration) Resource() data.Resource {
|
|||||||
func (d *Declaration) Apply(stateTransition string) (result error) {
|
func (d *Declaration) Apply(stateTransition string) (result error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
slog.Debug("Declaration.Apply()", "stacktrace", string(debug.Stack()))
|
|
||||||
slog.Info("Declaration.Apply()", "error", r, "resourceerror", d.Error)
|
slog.Info("Declaration.Apply()", "error", r, "resourceerror", d.Error)
|
||||||
if d.Error != "" {
|
if d.Error != "" {
|
||||||
result = fmt.Errorf("%s - %s", r, d.Error)
|
result = fmt.Errorf("%s - %s", r, d.Error)
|
||||||
@ -245,7 +248,7 @@ func (d *Declaration) SetConfig(configDoc data.Document) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if d.Config != "" { // XXX
|
if d.Config != "" { // XXX
|
||||||
panic("failed setting config")
|
panic(fmt.Sprintf("failed setting config: %s", d.Config))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +257,7 @@ func (d *Declaration) SetURI(uri string) (err error) {
|
|||||||
if d.Attributes == nil {
|
if d.Attributes == nil {
|
||||||
err = d.NewResource(&uri)
|
err = d.NewResource(&uri)
|
||||||
} else {
|
} else {
|
||||||
err = d.Attributes.SetURI(uri)
|
err = d.Attributes.SetParsedURI(URI(uri).Parse())
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -268,7 +271,7 @@ func (d *Declaration) SetURI(uri string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) SetParsedURI(uri *url.URL) (err error) {
|
func (d *Declaration) SetParsedURI(uri data.URIParser) (err error) {
|
||||||
slog.Info("Declaration.SetParsedURI()", "uri", uri, "declaration", d)
|
slog.Info("Declaration.SetParsedURI()", "uri", uri, "declaration", d)
|
||||||
if d.Attributes == nil {
|
if d.Attributes == nil {
|
||||||
err = d.NewResourceFromParsedURI(uri)
|
err = d.NewResourceFromParsedURI(uri)
|
||||||
@ -298,7 +301,9 @@ func (d *Declaration) UnmarshalValue(value *DeclarationType) error {
|
|||||||
d.OnError = value.OnError
|
d.OnError = value.OnError
|
||||||
d.Error = value.Error
|
d.Error = value.Error
|
||||||
d.Requires = value.Requires
|
d.Requires = value.Requires
|
||||||
newResource, resourceErr := d.ResourceTypes.New(fmt.Sprintf("%s://", value.Type))
|
d.On = value.On
|
||||||
|
newResource, resourceErr := d.ResourceTypes.NewFromType(string(value.Type))
|
||||||
|
|
||||||
slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr, "type", value.Type, "resource", newResource, "resourcetypes", d.ResourceTypes)
|
slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr, "type", value.Type, "resource", newResource, "resourcetypes", d.ResourceTypes)
|
||||||
if resourceErr != nil {
|
if resourceErr != nil {
|
||||||
slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr)
|
slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr)
|
||||||
@ -306,10 +311,30 @@ func (d *Declaration) UnmarshalValue(value *DeclarationType) error {
|
|||||||
}
|
}
|
||||||
d.Attributes = newResource
|
d.Attributes = newResource
|
||||||
d.configBlock = d.Config.GetBlock()
|
d.configBlock = d.Config.GetBlock()
|
||||||
|
|
||||||
|
if d.On != nil {
|
||||||
|
if handler, ok := (*d.On)[EventTypeLoad]; ok {
|
||||||
|
stackSize := d.runtime.Api().GetTop()
|
||||||
|
if e := d.runtime.LoadScriptFromString(string(handler)); e != nil {
|
||||||
|
d.Error = e.Error()
|
||||||
|
}
|
||||||
|
returnsCount := d.runtime.Api().GetTop() - stackSize
|
||||||
|
if ! d.runtime.Api().IsNil(-1) {
|
||||||
|
if returnsCount == 0 {
|
||||||
|
// return nil
|
||||||
|
} else {
|
||||||
|
if lr,le := d.runtime.CopyReturnValuesFromCall(int(returnsCount)); le == nil {
|
||||||
|
slog.Info("Event.Load", "result", lr, "error", le)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) UnmarshalYAML(value *yaml.Node) error {
|
func (d *Declaration) UnmarshalYAML(value *yaml.Node) (err error) {
|
||||||
if d.ResourceTypes == nil {
|
if d.ResourceTypes == nil {
|
||||||
d.ResourceTypes = DocumentRegistry.ResourceTypes
|
d.ResourceTypes = DocumentRegistry.ResourceTypes
|
||||||
}
|
}
|
||||||
@ -331,10 +356,17 @@ func (d *Declaration) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
if unmarshalResourceErr := resourceAttrs.Attributes.Decode(d.Attributes); unmarshalResourceErr != nil {
|
if unmarshalResourceErr := resourceAttrs.Attributes.Decode(d.Attributes); unmarshalResourceErr != nil {
|
||||||
return unmarshalResourceErr
|
return unmarshalResourceErr
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
if i, ok := d.Attributes.(data.ResourceInitializer); ok {
|
||||||
|
err = i.Init(nil)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("failed to execute init")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) UnmarshalJSON(jsonData []byte) error {
|
func (d *Declaration) UnmarshalJSON(jsonData []byte) (err error) {
|
||||||
if d.ResourceTypes == nil {
|
if d.ResourceTypes == nil {
|
||||||
d.ResourceTypes = DocumentRegistry.ResourceTypes
|
d.ResourceTypes = DocumentRegistry.ResourceTypes
|
||||||
}
|
}
|
||||||
@ -354,24 +386,14 @@ func (d *Declaration) UnmarshalJSON(jsonData []byte) error {
|
|||||||
return unmarshalAttributesErr
|
return unmarshalAttributesErr
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if i, ok := d.Attributes.(data.ResourceInitializer); ok {
|
||||||
}
|
err = i.Init(nil)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("failed to execute init")
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
return
|
||||||
func (d *Declaration) MarshalJSON() ([]byte, error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
buf.WriteByte('"')
|
|
||||||
buf.WriteString("value"))
|
|
||||||
buf.WriteByte('"')
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
func (d *Declaration) MarshalYAML() (any, error) {
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
func (l *LuaWorker) Receive(m message.Envelope) {
|
func (l *LuaWorker) Receive(m message.Envelope) {
|
||||||
|
@ -57,6 +57,7 @@ func TestNewResourceDeclarationType(t *testing.T) {
|
|||||||
|
|
||||||
e := resourceDeclaration.LoadString(decl, codec.FormatYaml)
|
e := resourceDeclaration.LoadString(decl, codec.FormatYaml)
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
|
|
||||||
assert.Equal(t, TypeName("foo"), resourceDeclaration.Type)
|
assert.Equal(t, TypeName("foo"), resourceDeclaration.Type)
|
||||||
assert.NotNil(t, resourceDeclaration.Attributes)
|
assert.NotNil(t, resourceDeclaration.Attributes)
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
_ "os"
|
_ "os"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/url"
|
|
||||||
"github.com/sters/yaml-diff/yamldiff"
|
"github.com/sters/yaml-diff/yamldiff"
|
||||||
"strings"
|
"strings"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
@ -19,6 +18,7 @@ _ "decl/internal/types"
|
|||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/schema"
|
"decl/internal/schema"
|
||||||
"context"
|
"context"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DocumentType struct {
|
type DocumentType struct {
|
||||||
@ -44,13 +44,23 @@ type Document struct {
|
|||||||
config data.Document
|
config data.Document
|
||||||
Registry *Registry `json:"-" yaml:"-"`
|
Registry *Registry `json:"-" yaml:"-"`
|
||||||
failedResources int `json:"-" yaml:"-"`
|
failedResources int `json:"-" yaml:"-"`
|
||||||
|
importPaths *SearchPath `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDocument(r *Registry) *Document {
|
func NewDocument(r *Registry) *Document {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
r = DocumentRegistry
|
r = DocumentRegistry
|
||||||
}
|
}
|
||||||
return &Document{ Registry: r, Format: codec.FormatYaml, uris: mapper.New[string, data.Declaration](), configNames: mapper.New[string, data.Block]() }
|
|
||||||
|
var configImportPath ConfigKey = "system.importpath"
|
||||||
|
|
||||||
|
return &Document{
|
||||||
|
Registry: r,
|
||||||
|
Format: codec.FormatYaml,
|
||||||
|
uris: mapper.New[string, data.Declaration](),
|
||||||
|
configNames: mapper.New[string, data.Block](),
|
||||||
|
importPaths: NewSearchPath(configImportPath.GetStringSlice()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) GetURI() string {
|
func (d *Document) GetURI() string {
|
||||||
@ -59,6 +69,19 @@ func (d *Document) GetURI() string {
|
|||||||
|
|
||||||
func (d *Document) SetURI(uri string) {
|
func (d *Document) SetURI(uri string) {
|
||||||
d.URI = URI(uri)
|
d.URI = URI(uri)
|
||||||
|
d.AddProjectPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Document) AddProjectPath() {
|
||||||
|
exists := d.URI.Exists()
|
||||||
|
if exists {
|
||||||
|
if u := d.URI.Parse().(*ParsedURI); u != nil {
|
||||||
|
projectPath := filepath.Dir(filepath.Join(u.Hostname(), u.Path))
|
||||||
|
if err := d.importPaths.AddPath(projectPath); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Types() data.TypesRegistry[data.Resource] {
|
func (d *Document) Types() data.TypesRegistry[data.Resource] {
|
||||||
@ -99,6 +122,10 @@ func (d *Document) Set(key string, value any) {
|
|||||||
d.uris.Set(key, value.(data.Declaration))
|
d.uris.Set(key, value.(data.Declaration))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Document) Delete(key string) {
|
||||||
|
d.uris.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Document) GetResource(uri string) *Declaration {
|
func (d *Document) GetResource(uri string) *Declaration {
|
||||||
if decl, ok := d.uris[uri]; ok {
|
if decl, ok := d.uris[uri]; ok {
|
||||||
return decl.(*Declaration)
|
return decl.(*Declaration)
|
||||||
@ -142,7 +169,15 @@ func (d *Document) ImportedDocuments() (documents []data.Document) {
|
|||||||
func (d *Document) loadImports() (err error) {
|
func (d *Document) loadImports() (err error) {
|
||||||
for _, uri := range d.Imports {
|
for _, uri := range d.Imports {
|
||||||
if ! DocumentRegistry.HasDocument(uri) {
|
if ! DocumentRegistry.HasDocument(uri) {
|
||||||
if _, err = DocumentRegistry.Load(uri); err != nil {
|
var load URI = uri
|
||||||
|
if ! load.Exists() {
|
||||||
|
foundURI := d.importPaths.FindURI(load)
|
||||||
|
if foundURI != "" {
|
||||||
|
load = foundURI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slog.Info("Document.loadImports()", "load", load, "uri", uri, "importpaths", d.importPaths, "doc", d.URI)
|
||||||
|
if _, err = DocumentRegistry.Load(load); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,6 +358,10 @@ func (d *Document) MapResourceURI(uri string, declaration data.Declaration) {
|
|||||||
d.uris[uri] = declaration
|
d.uris[uri] = declaration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Document) UnMapResourceURI(uri string) {
|
||||||
|
d.uris.Delete(uri)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Document) AddDeclaration(declaration data.Declaration) {
|
func (d *Document) AddDeclaration(declaration data.Declaration) {
|
||||||
uri := declaration.URI()
|
uri := declaration.URI()
|
||||||
decl := declaration.(*Declaration)
|
decl := declaration.(*Declaration)
|
||||||
@ -382,7 +421,7 @@ func (d *Document) NewResourceFromURI(uri URI) (newResource data.Resource, err e
|
|||||||
return d.NewResourceFromParsedURI(uri.Parse())
|
return d.NewResourceFromParsedURI(uri.Parse())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) NewResourceFromParsedURI(uri *url.URL) (newResource data.Resource, err error) {
|
func (d *Document) NewResourceFromParsedURI(uri data.URIParser) (newResource data.Resource, err error) {
|
||||||
if uri == nil {
|
if uri == nil {
|
||||||
return nil, fmt.Errorf("%w: %s", ErrUnknownResourceType, uri)
|
return nil, fmt.Errorf("%w: %s", ErrUnknownResourceType, uri)
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,9 @@ resources:
|
|||||||
|
|
||||||
f.Name = "mytestresource"
|
f.Name = "mytestresource"
|
||||||
f.Size = 3
|
f.Size = 3
|
||||||
|
|
||||||
|
assert.Nil(t, f.Init(nil))
|
||||||
|
|
||||||
d.AddResourceDeclaration("foo", f)
|
d.AddResourceDeclaration("foo", f)
|
||||||
|
|
||||||
ey := d.Generate(&documentYaml)
|
ey := d.Generate(&documentYaml)
|
||||||
|
31
internal/folio/events.go
Normal file
31
internal/folio/events.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
_ "gitea.rosskeen.house/pylon/luaruntime"
|
||||||
|
_ "fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventHandler string
|
||||||
|
|
||||||
|
type Events map[EventType]EventHandler
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidHandler error = errors.New("Invalid event handler")
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewEvents() *Events {
|
||||||
|
e := make(Events)
|
||||||
|
return &e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Events) Set(t EventType, h EventHandler) (err error) {
|
||||||
|
if err = t.Validate(); err == nil {
|
||||||
|
(*e)[t] = h
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
22
internal/folio/events_test.go
Normal file
22
internal/folio/events_test.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestNewEvent(t *testing.T) {
|
||||||
|
var et EventType
|
||||||
|
events := NewEvents()
|
||||||
|
assert.NotNil(t, events)
|
||||||
|
|
||||||
|
assert.Nil(t, et.Set(EventTypeLoad))
|
||||||
|
assert.Nil(t, events.Set(et, EventHandler(`
|
||||||
|
print('hello world')
|
||||||
|
`)))
|
||||||
|
|
||||||
|
}
|
39
internal/folio/eventtype.go
Normal file
39
internal/folio/eventtype.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventTypeLoad EventType = "load"
|
||||||
|
EventTypeCreate EventType = "create"
|
||||||
|
EventTypeRead EventType = "read"
|
||||||
|
EventTypeUpdate EventType = "update"
|
||||||
|
EventTypeDelete EventType = "delete"
|
||||||
|
EventTypeError EventType = "error"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnknownEventType error = errors.New("Unknown EventType")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e EventType) Validate() (err error) {
|
||||||
|
switch e {
|
||||||
|
case EventTypeLoad, EventTypeCreate, EventTypeRead, EventTypeUpdate, EventTypeDelete, EventTypeError:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %s", ErrUnknownEventType, e)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EventType) Set(v EventType) (err error) {
|
||||||
|
if err = v.Validate(); err == nil {
|
||||||
|
(*e) = v
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
29
internal/folio/eventtype_test.go
Normal file
29
internal/folio/eventtype_test.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestEventType(t *testing.T) {
|
||||||
|
for _, v := range []struct{ et EventType; expected error } {
|
||||||
|
{ et: EventType("load"), expected: nil },
|
||||||
|
{ et: EventType("create"), expected: nil },
|
||||||
|
{ et: EventType("read"), expected: nil },
|
||||||
|
{ et: EventType("update"), expected: nil },
|
||||||
|
{ et: EventType("delete"), expected: nil },
|
||||||
|
{ et: EventType("error"), expected: nil },
|
||||||
|
{ et: EventType("foo"), expected: ErrUnknownEventType },
|
||||||
|
} {
|
||||||
|
assert.ErrorIs(t, v.et.Validate(), v.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventTypeSet(t *testing.T) {
|
||||||
|
var et EventType
|
||||||
|
assert.Nil(t, et.Set(EventTypeLoad))
|
||||||
|
}
|
@ -3,14 +3,13 @@
|
|||||||
package folio
|
package folio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "gopkg.in/yaml.v3"
|
_ "gopkg.in/yaml.v3"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
_ "fmt"
|
_ "fmt"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MockConfiguration struct {
|
type MockConfiguration struct {
|
||||||
@ -38,7 +37,7 @@ func (m *MockConfiguration) SetURI(uri string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockConfiguration) SetParsedURI(uri *url.URL) error {
|
func (m *MockConfiguration) SetParsedURI(uri data.URIParser) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,22 +17,30 @@ _ "gopkg.in/yaml.v3"
|
|||||||
func RegisterMocks() {
|
func RegisterMocks() {
|
||||||
TestResourceTypes.Register([]string{"foo"}, func(u *url.URL) data.Resource {
|
TestResourceTypes.Register([]string{"foo"}, func(u *url.URL) data.Resource {
|
||||||
f := NewFooResource()
|
f := NewFooResource()
|
||||||
f.Name = filepath.Join(u.Hostname(), u.Path)
|
if u != nil {
|
||||||
|
f.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
|
}
|
||||||
return f
|
return f
|
||||||
})
|
})
|
||||||
TestResourceTypes.Register([]string{"bar"}, func(u *url.URL) data.Resource {
|
TestResourceTypes.Register([]string{"bar"}, func(u *url.URL) data.Resource {
|
||||||
f := NewBarResource()
|
f := NewBarResource()
|
||||||
f.Name = filepath.Join(u.Hostname(), u.Path)
|
if u != nil {
|
||||||
|
f.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
|
}
|
||||||
return f
|
return f
|
||||||
})
|
})
|
||||||
TestResourceTypes.Register([]string{"testuser"}, func(u *url.URL) data.Resource {
|
TestResourceTypes.Register([]string{"testuser"}, func(u *url.URL) data.Resource {
|
||||||
f := NewTestuserResource()
|
f := NewTestuserResource()
|
||||||
f.Name = filepath.Join(u.Hostname(), u.Path)
|
if u != nil {
|
||||||
|
f.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
|
}
|
||||||
return f
|
return f
|
||||||
})
|
})
|
||||||
TestResourceTypes.Register([]string{"file"}, func(u *url.URL) data.Resource {
|
TestResourceTypes.Register([]string{"file"}, func(u *url.URL) data.Resource {
|
||||||
f := NewFileResource()
|
f := NewFileResource()
|
||||||
f.Name = filepath.Join(u.Hostname(), u.Path)
|
if u != nil {
|
||||||
|
f.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
|
}
|
||||||
return f
|
return f
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -40,23 +48,20 @@ func RegisterMocks() {
|
|||||||
|
|
||||||
type MockFoo struct {
|
type MockFoo struct {
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
*MockResource `json:"-" yaml:"-"`
|
*MockResource `json:",inline" yaml:",inline"`
|
||||||
Name string `json:"name" yaml:"name"`
|
|
||||||
Size int `json:"size" yaml:"size"`
|
Size int `json:"size" yaml:"size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockBar struct {
|
type MockBar struct {
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
*MockResource `json:"-" yaml:"-"`
|
*MockResource `json:",inline" yaml:",inline"`
|
||||||
Name string `json:"name" yaml:"name"`
|
|
||||||
Size int `json:"size,omitempty" yaml:"size,omitempty"`
|
Size int `json:"size,omitempty" yaml:"size,omitempty"`
|
||||||
Owner string `json:"owner,omitempty" yaml:"owner,omitempty"`
|
Owner string `json:"owner,omitempty" yaml:"owner,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockTestuser struct {
|
type MockTestuser struct {
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
*MockResource `json:"-" yaml:"-"`
|
*MockResource `json:",inline" yaml:",inline"`
|
||||||
Name string `json:"name" yaml:"name"`
|
|
||||||
Uid string `json:"uid" yaml:"uid"`
|
Uid string `json:"uid" yaml:"uid"`
|
||||||
Group string `json:"group" yaml:"group"`
|
Group string `json:"group" yaml:"group"`
|
||||||
Home string `json:"home" yaml:"home"`
|
Home string `json:"home" yaml:"home"`
|
||||||
@ -64,13 +69,12 @@ type MockTestuser struct {
|
|||||||
|
|
||||||
type MockFile struct {
|
type MockFile struct {
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
*MockResource `json:"-" yaml:"-"`
|
*MockResource `json:",inline" yaml:",inline"`
|
||||||
Name string `json:"name" yaml:"name"`
|
|
||||||
Size int `json:"size" yaml:"size"`
|
Size int `json:"size" yaml:"size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMockResource(typename string, stater machine.Stater) *MockResource {
|
func NewMockResource(typename string, stater machine.Stater) (m *MockResource) {
|
||||||
return &MockResource {
|
m = &MockResource {
|
||||||
InjectType: func() string { return typename },
|
InjectType: func() string { return typename },
|
||||||
InjectResolveId: func(ctx context.Context) string { return "bar" },
|
InjectResolveId: func(ctx context.Context) string { return "bar" },
|
||||||
InjectLoadDecl: func(string) error { return nil },
|
InjectLoadDecl: func(string) error { return nil },
|
||||||
@ -89,6 +93,13 @@ func NewMockResource(typename string, stater machine.Stater) *MockResource {
|
|||||||
InjectURI: func() string { return fmt.Sprintf("%s://bar", typename) },
|
InjectURI: func() string { return fmt.Sprintf("%s://bar", typename) },
|
||||||
InjectNotify: func(*machine.EventMessage) {},
|
InjectNotify: func(*machine.EventMessage) {},
|
||||||
}
|
}
|
||||||
|
m.InjectInit = func(u data.URIParser) error {
|
||||||
|
if u != nil {
|
||||||
|
m.Name = filepath.Join(u.URL().Hostname(), u.URL().Path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFooResource() *MockFoo {
|
func NewFooResource() *MockFoo {
|
||||||
|
@ -3,18 +3,19 @@
|
|||||||
package folio
|
package folio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "gopkg.in/yaml.v3"
|
_ "gopkg.in/yaml.v3"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
_ "fmt"
|
_ "fmt"
|
||||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MockResource struct {
|
type MockResource struct {
|
||||||
|
Name string `json:"name" yaml:"name"`
|
||||||
|
InjectInit func(data.URIParser) error `json:"-" yaml:"-"`
|
||||||
InjectURI func() string `json:"-" yaml:"-"`
|
InjectURI func() string `json:"-" yaml:"-"`
|
||||||
InjectType func() string `json:"-" yaml:"-"`
|
InjectType func() string `json:"-" yaml:"-"`
|
||||||
InjectResolveId func(ctx context.Context) string `json:"-" yaml:"-"`
|
InjectResolveId func(ctx context.Context) string `json:"-" yaml:"-"`
|
||||||
@ -54,7 +55,7 @@ func (m *MockResource) SetURI(uri string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) SetParsedURI(uri *url.URL) error {
|
func (m *MockResource) SetParsedURI(uri data.URIParser) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +127,10 @@ func (m *MockResource) Apply() error {
|
|||||||
return m.InjectApply()
|
return m.InjectApply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockResource) Init(u data.URIParser) (error) {
|
||||||
|
return m.InjectInit(u)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MockResource) Type() string {
|
func (m *MockResource) Type() string {
|
||||||
return m.InjectType()
|
return m.InjectType()
|
||||||
}
|
}
|
||||||
|
102
internal/folio/parseduri.go
Normal file
102
internal/folio/parseduri.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"decl/internal/transport"
|
||||||
|
"decl/internal/data"
|
||||||
|
"decl/internal/identifier"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
)
|
||||||
|
|
||||||
|
type ParsedURI url.URL
|
||||||
|
|
||||||
|
func NewParsedURI(uri URI) *ParsedURI {
|
||||||
|
return uri.Parse().(*ParsedURI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CastParsedURI(u *url.URL) *ParsedURI {
|
||||||
|
return (*ParsedURI)(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) URL() *url.URL {
|
||||||
|
return (*url.URL)(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) Hostname() string {
|
||||||
|
return (*url.URL)(uri).Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) NewResource(document data.Document) (newResource data.Resource, err error) {
|
||||||
|
if document == nil {
|
||||||
|
declaration := NewDeclaration()
|
||||||
|
if err = declaration.NewResourceFromParsedURI(uri); err == nil {
|
||||||
|
return declaration.Attributes, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newResource, err = document.NewResourceFromParsedURI(uri)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) ConstructResource(res data.Resource) (err error) {
|
||||||
|
if ri, ok := res.(data.ResourceInitializer); ok {
|
||||||
|
err = ri.Init(uri)
|
||||||
|
} else {
|
||||||
|
err = res.SetParsedURI(uri)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) Converter() (converter data.Converter, err error) {
|
||||||
|
return DocumentRegistry.ConverterTypes.NewFromParsedURI(uri.URL())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) Exists() bool {
|
||||||
|
return transport.Exists(uri.URL())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) ContentReaderStream() (*transport.Reader, error) {
|
||||||
|
return transport.NewReader(uri.URL())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) ContentWriterStream() (*transport.Writer, error) {
|
||||||
|
return transport.NewWriter(uri.URL())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) String() string {
|
||||||
|
return uri.URL().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) SetURL(url *url.URL) {
|
||||||
|
*uri = *(*ParsedURI)(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) Extension() (string, string) {
|
||||||
|
return (identifier.ID)(uri.Path).Extension()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) ContentType() string {
|
||||||
|
var ext strings.Builder
|
||||||
|
exttype, fileext := uri.Extension()
|
||||||
|
if fileext == "" {
|
||||||
|
return exttype
|
||||||
|
}
|
||||||
|
ext.WriteString(exttype)
|
||||||
|
ext.WriteRune('.')
|
||||||
|
ext.WriteString(fileext)
|
||||||
|
return ext.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri *ParsedURI) IsEmpty() bool {
|
||||||
|
if uri == nil || (identifier.ID)(uri.String()).IsEmpty() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -69,7 +69,7 @@ func (r *Registry) SetDocument(key URI, value *Document) {
|
|||||||
|
|
||||||
func (r *Registry) NewDocument(uri URI) (doc *Document) {
|
func (r *Registry) NewDocument(uri URI) (doc *Document) {
|
||||||
doc = NewDocument(r)
|
doc = NewDocument(r)
|
||||||
doc.URI = uri
|
doc.SetURI(string(uri))
|
||||||
r.Documents = append(r.Documents, doc)
|
r.Documents = append(r.Documents, doc)
|
||||||
if uri != "" {
|
if uri != "" {
|
||||||
r.UriMap[uri] = doc
|
r.UriMap[uri] = doc
|
||||||
@ -88,17 +88,17 @@ func (r *Registry) AppendParsedURI(uri *url.URL, documents []data.Document) (add
|
|||||||
case data.ManyExtractor:
|
case data.ManyExtractor:
|
||||||
var docs []data.Document
|
var docs []data.Document
|
||||||
docs, err = extractor.ExtractMany(sourceResource, nil)
|
docs, err = extractor.ExtractMany(sourceResource, nil)
|
||||||
slog.Info("folio.Registry.Append() - ExtractMany", "uri", uri, "source", sourceResource, "docs", docs, "error", err)
|
slog.Info("folio.Registry.AppendParsedURI() - ExtractMany", "uri", uri, "source", sourceResource, "docs", docs, "error", err)
|
||||||
documents = append(documents, docs...)
|
documents = append(documents, docs...)
|
||||||
case data.Extractor:
|
case data.Extractor:
|
||||||
var singleDocument data.Document
|
var singleDocument data.Document
|
||||||
singleDocument, err = extractor.Extract(sourceResource, nil)
|
singleDocument, err = extractor.Extract(sourceResource, nil)
|
||||||
slog.Info("folio.Registry.Append() - Extract", "uri", uri, "source", sourceResource, "doc", singleDocument, "error", err)
|
slog.Info("folio.Registry.AppendParsedURI() - Extract", "uri", uri, "source", sourceResource, "doc", singleDocument, "error", err)
|
||||||
documents = append(documents, singleDocument)
|
documents = append(documents, singleDocument)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slog.Info("folio.Registry.Append()", "uri", uri, "converter", r.ConverterTypes, "error", err)
|
slog.Info("folio.Registry.AppendParsedURI()", "uri", uri, "converter", r.ConverterTypes, "error", err)
|
||||||
addedDocuments = documents
|
addedDocuments = documents
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -130,6 +130,8 @@ func (r *Registry) Append(uri URI, documents []data.Document) (addedDocuments []
|
|||||||
slog.Info("folio.Registry.Append() - Extract", "uri", uri, "source", sourceResource, "doc", singleDocument, "error", err)
|
slog.Info("folio.Registry.Append() - Extract", "uri", uri, "source", sourceResource, "doc", singleDocument, "error", err)
|
||||||
documents = append(documents, singleDocument)
|
documents = append(documents, singleDocument)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
slog.Warn("folio.Registry.Append(): failed loading extractor as resource")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slog.Info("folio.Registry.Append()", "uri", uri, "converter", r.ConverterTypes, "error", err)
|
slog.Info("folio.Registry.Append()", "uri", uri, "converter", r.ConverterTypes, "error", err)
|
||||||
@ -146,3 +148,8 @@ func (r *Registry) LoadFromURL(uri *url.URL) (documents []data.Document, err err
|
|||||||
documents = make([]data.Document, 0, 10)
|
documents = make([]data.Document, 0, 10)
|
||||||
return r.AppendParsedURI(uri, documents)
|
return r.AppendParsedURI(uri, documents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Registry) LoadFromParsedURI(uri *url.URL) (documents []data.Document, err error) {
|
||||||
|
documents = make([]data.Document, 0, 10)
|
||||||
|
return r.AppendParsedURI(uri, documents)
|
||||||
|
}
|
||||||
|
@ -15,11 +15,11 @@ var (
|
|||||||
func NewResourceFromParsedURI(u *url.URL, document data.Document) (newResource data.Resource, err error) {
|
func NewResourceFromParsedURI(u *url.URL, document data.Document) (newResource data.Resource, err error) {
|
||||||
if document == nil {
|
if document == nil {
|
||||||
declaration := NewDeclaration()
|
declaration := NewDeclaration()
|
||||||
if err = declaration.NewResourceFromParsedURI(u); err == nil {
|
if err = declaration.NewResourceFromParsedURI((*ParsedURI)(u)); err == nil {
|
||||||
return declaration.Attributes, err
|
return declaration.Attributes, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newResource, err = document.NewResourceFromParsedURI(u)
|
newResource, err = document.NewResourceFromParsedURI((*ParsedURI)(u))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -56,7 +56,7 @@ func (r ResourceReference) Dereference(look data.ResourceMapper) data.Resource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r ResourceReference) Parse() *url.URL {
|
func (r ResourceReference) Parse() *url.URL {
|
||||||
return URI(r).Parse()
|
return URI(r).Parse().URL()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r ResourceReference) Exists() bool {
|
func (r ResourceReference) Exists() bool {
|
||||||
|
65
internal/folio/searchpath.go
Normal file
65
internal/folio/searchpath.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"decl/internal/ext"
|
||||||
|
"decl/internal/ds"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"path/filepath"
|
||||||
|
"decl/internal/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSearchPathNotExist error = errors.New("Search path does not exist.")
|
||||||
|
)
|
||||||
|
|
||||||
|
// A search path should find a file in list of paths
|
||||||
|
|
||||||
|
type SearchPath struct {
|
||||||
|
paths *ds.OrderedSet[string]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSearchPath(paths []string) (s *SearchPath) {
|
||||||
|
s = &SearchPath{ paths: ds.NewOrderedSet[string]() }
|
||||||
|
s.paths.AddItems(paths)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SearchPath) AddPath(path string) error {
|
||||||
|
if newPath := ext.FilePath(path).Abs(); newPath.Exists() {
|
||||||
|
s.paths.Add(string(newPath))
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("%w: %s", ErrSearchPathNotExist, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SearchPath) Find(relative string) string {
|
||||||
|
pathValues := s.paths.Items()
|
||||||
|
for i := len(pathValues) - 1; i >= 0; i-- {
|
||||||
|
p := *pathValues[i]
|
||||||
|
searchPath := ext.FilePath(p)
|
||||||
|
searchPath.Add(relative)
|
||||||
|
slog.Info("SearchPath.Find()", "searchpath", p, "file", relative, "target", searchPath)
|
||||||
|
if searchPath.Exists() {
|
||||||
|
return string(searchPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SearchPath) FindParsedURI(uri data.URIParser) (result URI) {
|
||||||
|
u := uri.URL()
|
||||||
|
filePath := filepath.Join(u.Hostname(), u.Path)
|
||||||
|
if absPath := s.Find(filePath); absPath != "" {
|
||||||
|
result = URI(fmt.Sprintf("%s://%s", u.Scheme, string(absPath)))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SearchPath) FindURI(uri URI) (result URI) {
|
||||||
|
return s.FindParsedURI(uri.Parse())
|
||||||
|
}
|
24
internal/folio/searchpath_test.go
Normal file
24
internal/folio/searchpath_test.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package folio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSearchPath(t *testing.T) {
|
||||||
|
|
||||||
|
assert.Nil(t, TempDir.CreateFile("test.jx.yaml", ""))
|
||||||
|
|
||||||
|
sp := NewSearchPath(nil)
|
||||||
|
assert.NotNil(t, sp)
|
||||||
|
|
||||||
|
assert.Nil(t, sp.AddPath(string(TempDir)))
|
||||||
|
|
||||||
|
absPath := sp.Find("test.jx.yaml")
|
||||||
|
_, err := os.Stat(absPath)
|
||||||
|
assert.True(t, !os.IsNotExist(err))
|
||||||
|
|
||||||
|
}
|
@ -35,14 +35,24 @@ func (u URI) NewResource(document data.Document) (newResource data.Resource, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u URI) ConstructResource(res data.Resource) (err error) {
|
||||||
|
parsedURI := u.Parse()
|
||||||
|
if ri, ok := res.(data.ResourceInitializer); ok {
|
||||||
|
err = ri.Init(parsedURI)
|
||||||
|
} else {
|
||||||
|
err = res.SetParsedURI(parsedURI)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (u URI) Converter() (converter data.Converter, err error) {
|
func (u URI) Converter() (converter data.Converter, err error) {
|
||||||
return DocumentRegistry.ConverterTypes.New(string(u))
|
return DocumentRegistry.ConverterTypes.New(string(u))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u URI) Parse() *url.URL {
|
func (u URI) Parse() data.URIParser {
|
||||||
url, e := url.Parse(string(u))
|
url, e := url.Parse(string(u))
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return url
|
return (*ParsedURI)(url)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -10,17 +10,17 @@ import (
|
|||||||
|
|
||||||
func TestURI(t *testing.T) {
|
func TestURI(t *testing.T) {
|
||||||
var file URI = URI(fmt.Sprintf("file://%s", TempDir))
|
var file URI = URI(fmt.Sprintf("file://%s", TempDir))
|
||||||
u := file.Parse()
|
u := file.Parse().URL()
|
||||||
assert.Equal(t, "file", u.Scheme)
|
assert.Equal(t, "file", u.Scheme)
|
||||||
assert.True(t, file.Exists())
|
assert.True(t, file.Exists())
|
||||||
file = URI(fmt.Sprintf("0file:_/%s", TempDir))
|
file = URI(fmt.Sprintf("0file:_/%s", TempDir))
|
||||||
u = file.Parse()
|
x := file.Parse()
|
||||||
assert.Nil(t, u)
|
assert.Nil(t, x)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestURISetURL(t *testing.T) {
|
func TestURISetURL(t *testing.T) {
|
||||||
var file URI = URI(fmt.Sprintf("file://%s", TempDir))
|
var file URI = URI(fmt.Sprintf("file://%s", TempDir))
|
||||||
u := file.Parse()
|
u := file.Parse().URL()
|
||||||
var fileFromURL URI
|
var fileFromURL URI
|
||||||
fileFromURL.SetURL(u)
|
fileFromURL.SetURL(u)
|
||||||
assert.Equal(t, fileFromURL, file)
|
assert.Equal(t, fileFromURL, file)
|
||||||
|
@ -18,10 +18,15 @@ func (m Store[Key, Value]) Set(key Key, value Value) {
|
|||||||
m[key] = value
|
m[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Store[Key, Value]) Delete(key Key) {
|
||||||
|
delete(m, key)
|
||||||
|
}
|
||||||
|
|
||||||
type Mapper interface {
|
type Mapper interface {
|
||||||
Get(key string) (any, bool)
|
Get(key string) (any, bool)
|
||||||
Has(key string) (bool)
|
Has(key string) (bool)
|
||||||
Set(key string, value any)
|
Set(key string, value any)
|
||||||
|
Delete(key string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Getter[Key comparable, Value comparable] interface {
|
type Getter[Key comparable, Value comparable] interface {
|
||||||
@ -33,6 +38,7 @@ type Map[Key comparable, Value comparable] interface {
|
|||||||
Get(key Key) (Value, bool)
|
Get(key Key) (Value, bool)
|
||||||
Has(key Key) (bool)
|
Has(key Key) (bool)
|
||||||
Set(key Key, value Value)
|
Set(key Key, value Value)
|
||||||
|
Delete(key Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New[Key comparable, Value comparable]() Store[Key, Value] {
|
func New[Key comparable, Value comparable]() Store[Key, Value] {
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package resource
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
_ "net/url"
|
|
||||||
_ "os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
"decl/internal/codec"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommandExecutor func(value any) ([]byte, error)
|
|
||||||
type CommandExtractAttributes func(output []byte, target any) error
|
|
||||||
|
|
||||||
type CommandArg string
|
|
||||||
|
|
||||||
type Command struct {
|
|
||||||
Path string `json:"path" yaml:"path"`
|
|
||||||
Args []CommandArg `json:"args" yaml:"args"`
|
|
||||||
Split bool `json:"split" yaml:"split"`
|
|
||||||
Executor CommandExecutor `json:"-" yaml:"-"`
|
|
||||||
Extractor CommandExtractAttributes `json:"-" yaml:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCommand() *Command {
|
|
||||||
c := &Command{ Split: true }
|
|
||||||
c.Executor = func(value any) ([]byte, error) {
|
|
||||||
args, err := c.Template(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cmd := exec.Command(c.Path, args...)
|
|
||||||
|
|
||||||
slog.Info("execute() - cmd", "path", c.Path, "args", args)
|
|
||||||
output, stdoutPipeErr := cmd.StdoutPipe()
|
|
||||||
if stdoutPipeErr != nil {
|
|
||||||
return nil, stdoutPipeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
stderr, pipeErr := cmd.StderrPipe()
|
|
||||||
if pipeErr != nil {
|
|
||||||
return nil, pipeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if startErr := cmd.Start(); startErr != nil {
|
|
||||||
return nil, startErr
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("execute() - start", "cmd", cmd)
|
|
||||||
stdOutOutput, _ := io.ReadAll(output)
|
|
||||||
stdErrOutput, _ := io.ReadAll(stderr)
|
|
||||||
|
|
||||||
slog.Info("execute() - io", "stdout", string(stdOutOutput), "stderr", string(stdErrOutput))
|
|
||||||
waitErr := cmd.Wait()
|
|
||||||
|
|
||||||
slog.Info("execute()", "path", c.Path, "args", args, "output", string(stdOutOutput), "error", string(stdErrOutput))
|
|
||||||
|
|
||||||
if len(stdErrOutput) > 0 {
|
|
||||||
return stdOutOutput, fmt.Errorf(string(stdErrOutput))
|
|
||||||
}
|
|
||||||
return stdOutOutput, waitErr
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Command) Load(r io.Reader) error {
|
|
||||||
return codec.NewYAMLDecoder(r).Decode(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Command) LoadDecl(yamlResourceDeclaration string) error {
|
|
||||||
return codec.NewYAMLStringDecoder(yamlResourceDeclaration).Decode(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Command) Template(value any) ([]string, error) {
|
|
||||||
var args []string = make([]string, 0, len(c.Args) * 2)
|
|
||||||
for i, arg := range c.Args {
|
|
||||||
var commandLineArg strings.Builder
|
|
||||||
err := template.Must(template.New(fmt.Sprintf("arg%d", i)).Parse(string(arg))).Execute(&commandLineArg, value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if commandLineArg.Len() > 0 {
|
|
||||||
var splitArg []string
|
|
||||||
if c.Split {
|
|
||||||
splitArg = strings.Split(commandLineArg.String(), " ")
|
|
||||||
} else {
|
|
||||||
splitArg = []string{commandLineArg.String()}
|
|
||||||
}
|
|
||||||
slog.Info("Template()", "split", splitArg, "len", len(splitArg))
|
|
||||||
args = append(args, splitArg...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("Template()", "Args", c.Args, "lencargs", len(c.Args), "args", args, "lenargs", len(args), "value", value)
|
|
||||||
return args, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Command) Execute(value any) ([]byte, error) {
|
|
||||||
return c.Executor(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommandArg) UnmarshalValue(value string) error {
|
|
||||||
*c = CommandArg(value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommandArg) UnmarshalJSON(data []byte) error {
|
|
||||||
var s string
|
|
||||||
if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil {
|
|
||||||
return unmarshalRouteTypeErr
|
|
||||||
}
|
|
||||||
return c.UnmarshalValue(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommandArg) UnmarshalYAML(value *yaml.Node) error {
|
|
||||||
var s string
|
|
||||||
if err := value.Decode(&s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.UnmarshalValue(s)
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
|
|
||||||
package resource
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "fmt"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
_ "os"
|
|
||||||
_ "strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewCommand(t *testing.T) {
|
|
||||||
c := NewCommand()
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCommandLoad(t *testing.T) {
|
|
||||||
c := NewCommand()
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
|
|
||||||
decl := `
|
|
||||||
path: find
|
|
||||||
args:
|
|
||||||
- "{{ .Path }}"
|
|
||||||
`
|
|
||||||
|
|
||||||
assert.Nil(t, c.LoadDecl(decl))
|
|
||||||
assert.Equal(t, "find", c.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCommandTemplate(t *testing.T) {
|
|
||||||
c := NewCommand()
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
|
|
||||||
decl := `
|
|
||||||
path: find
|
|
||||||
args:
|
|
||||||
- "{{ .Path }}"
|
|
||||||
`
|
|
||||||
|
|
||||||
assert.Nil(t, c.LoadDecl(decl))
|
|
||||||
assert.Equal(t, "find", c.Path)
|
|
||||||
assert.Equal(t, 1, len(c.Args))
|
|
||||||
|
|
||||||
f := NewFile()
|
|
||||||
f.Path = "./"
|
|
||||||
args, templateErr := c.Template(f)
|
|
||||||
assert.Nil(t, templateErr)
|
|
||||||
assert.Equal(t, 1, len(args))
|
|
||||||
|
|
||||||
assert.Equal(t, "./", string(args[0]))
|
|
||||||
|
|
||||||
out, err := c.Execute(f)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Greater(t, len(out), 0)
|
|
||||||
}
|
|
@ -13,14 +13,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UriSchemeValidator func(scheme string) bool
|
type UriSchemeValidator func(scheme string) bool
|
||||||
|
type UriNormalize func() error
|
||||||
|
|
||||||
type Common struct {
|
type Common struct {
|
||||||
SchemeCheck UriSchemeValidator `json:"-" yaml:"-"`
|
SchemeCheck UriSchemeValidator `json:"-" yaml:"-"`
|
||||||
|
NormalizePath UriNormalize `json:"-" yaml:"-"`
|
||||||
includeQueryParamsInURI bool `json:"-" yaml:"-"`
|
includeQueryParamsInURI bool `json:"-" yaml:"-"`
|
||||||
resourceType TypeName `json:"-" yaml:"-"`
|
resourceType TypeName `json:"-" yaml:"-"`
|
||||||
Uri folio.URI `json:"uri,omitempty" yaml:"uri,omitempty"`
|
|
||||||
parsedURI *url.URL `json:"-" yaml:"-"`
|
parsedURI *url.URL `json:"-" yaml:"-"`
|
||||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||||
|
absPath string `json:"-" yaml:"-"`
|
||||||
|
|
||||||
exttype string `json:"-" yaml:"-"`
|
exttype string `json:"-" yaml:"-"`
|
||||||
fileext string `json:"-" yaml:"-"`
|
fileext string `json:"-" yaml:"-"`
|
||||||
@ -34,6 +36,7 @@ type Common struct {
|
|||||||
func NewCommon(resourceType TypeName, includeQueryParams bool) *Common {
|
func NewCommon(resourceType TypeName, includeQueryParams bool) *Common {
|
||||||
c := &Common{ includeQueryParamsInURI: includeQueryParams, resourceType: resourceType }
|
c := &Common{ includeQueryParamsInURI: includeQueryParams, resourceType: resourceType }
|
||||||
c.SchemeCheck = c.IsValidResourceScheme
|
c.SchemeCheck = c.IsValidResourceScheme
|
||||||
|
c.NormalizePath = c.NormalizeFilePath
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,9 +57,13 @@ func (c *Common) SetResourceMapper(resources data.ResourceMapper) {
|
|||||||
|
|
||||||
func (c *Common) Clone() *Common {
|
func (c *Common) Clone() *Common {
|
||||||
return &Common {
|
return &Common {
|
||||||
Uri: c.Uri,
|
SchemeCheck: c.SchemeCheck,
|
||||||
|
NormalizePath: c.NormalizePath,
|
||||||
|
includeQueryParamsInURI: c.includeQueryParamsInURI,
|
||||||
|
resourceType: c.resourceType,
|
||||||
parsedURI: c.parsedURI,
|
parsedURI: c.parsedURI,
|
||||||
Path: c.Path,
|
Path: c.Path,
|
||||||
|
absPath: c.absPath,
|
||||||
exttype: c.exttype,
|
exttype: c.exttype,
|
||||||
fileext: c.fileext,
|
fileext: c.fileext,
|
||||||
normalizePath: c.normalizePath,
|
normalizePath: c.normalizePath,
|
||||||
@ -74,41 +81,35 @@ func (c *Common) URIPath() string {
|
|||||||
return c.Path
|
return c.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) URI() string {
|
func (c *Common) URI() folio.URI {
|
||||||
return string(c.Uri)
|
slog.Info("Common.URI", "parsed", c.parsedURI)
|
||||||
|
return folio.URI(c.parsedURI.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) SetURI(uri string) (err error) {
|
func (c *Common) SetParsedURI(u data.URIParser) (err error) {
|
||||||
c.SetURIFromString(uri)
|
|
||||||
err = c.SetParsedURI(c.Uri.Parse())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Common) SetURIFromString(uri string) {
|
|
||||||
c.Uri = folio.URI(uri)
|
|
||||||
c.exttype, c.fileext = c.Uri.Extension()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Common) SetParsedURI(u *url.URL) (err error) {
|
|
||||||
if u != nil {
|
if u != nil {
|
||||||
slog.Info("Common.SetParsedURI()", "parsed", u, "uri", c.Uri)
|
|
||||||
if c.Uri.IsEmpty() {
|
slog.Info("Common.SetParsedURI()", "parsed", u, "uri", c)
|
||||||
c.SetURIFromString(u.String())
|
|
||||||
}
|
c.parsedURI = u.URL()
|
||||||
c.parsedURI = u
|
|
||||||
|
c.exttype, c.fileext = u.Extension()
|
||||||
if c.SchemeCheck(c.parsedURI.Scheme) {
|
if c.SchemeCheck(c.parsedURI.Scheme) {
|
||||||
if c.includeQueryParamsInURI {
|
if c.includeQueryParamsInURI {
|
||||||
c.Path = filepath.Join(c.parsedURI.Hostname(), c.parsedURI.RequestURI())
|
c.Path = filepath.Join(c.parsedURI.Hostname(), c.parsedURI.RequestURI())
|
||||||
} else {
|
} else {
|
||||||
c.Path = filepath.Join(c.parsedURI.Hostname(), c.parsedURI.Path)
|
c.Path = filepath.Join(c.parsedURI.Hostname(), c.parsedURI.Path)
|
||||||
}
|
}
|
||||||
|
if c.absPath, err = filepath.Abs(c.Path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if err = c.NormalizePath(); err != nil {
|
if err = c.NormalizePath(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = fmt.Errorf("%w: %s is not a %s resource, parsed: %t", ErrInvalidResourceURI, c.Uri, c.Type(), (u != nil))
|
err = fmt.Errorf("%w: %s is not a %s resource, parsed: %t", ErrInvalidResourceURI, c.URI(), c.Type(), (u != nil))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,26 +118,27 @@ func (c *Common) UseConfig(config data.ConfigurationValueGetter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) ResolveId(ctx context.Context) string {
|
func (c *Common) ResolveId(ctx context.Context) string {
|
||||||
if e := c.NormalizePath(); e != nil {
|
var err error
|
||||||
panic(e)
|
if c.absPath, err = filepath.Abs(c.Path); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err = c.NormalizePath(); err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
return c.Path
|
return c.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) NormalizePath() error {
|
// Common path normalization for a file resource.
|
||||||
|
func (c *Common) NormalizeFilePath() (err error) {
|
||||||
if c.config != nil {
|
if c.config != nil {
|
||||||
if prefixPath, configErr := c.config.GetValue("prefix"); configErr == nil {
|
if prefixPath, configErr := c.config.GetValue("prefix"); configErr == nil {
|
||||||
c.Path = filepath.Join(prefixPath.(string), c.Path)
|
c.Path = filepath.Join(prefixPath.(string), c.Path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.normalizePath {
|
if c.normalizePath {
|
||||||
filePath, fileAbsErr := filepath.Abs(c.Path)
|
c.Path = c.absPath
|
||||||
if fileAbsErr == nil {
|
|
||||||
c.Path = filePath
|
|
||||||
}
|
|
||||||
return fileAbsErr
|
|
||||||
}
|
}
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) Type() string { return string(c.resourceType) }
|
func (c *Common) Type() string { return string(c.resourceType) }
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"decl/internal/folio"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewCommon(t *testing.T) {
|
func TestNewCommon(t *testing.T) {
|
||||||
@ -16,16 +17,15 @@ func TestNewCommon(t *testing.T) {
|
|||||||
func TestCommon(t *testing.T) {
|
func TestCommon(t *testing.T) {
|
||||||
expectedCommon := NewCommon(FileTypeName, false)
|
expectedCommon := NewCommon(FileTypeName, false)
|
||||||
expectedCommon.resourceType = "file"
|
expectedCommon.resourceType = "file"
|
||||||
expectedCommon.Uri = "file:///tmp/foo"
|
|
||||||
expectedCommon.parsedURI = &url.URL{ Scheme: "file", Path: "/tmp/foo"}
|
expectedCommon.parsedURI = &url.URL{ Scheme: "file", Path: "/tmp/foo"}
|
||||||
expectedCommon.Path = "/tmp/foo"
|
expectedCommon.Path = "/tmp/foo"
|
||||||
for _, v := range []struct{ uri string; expected *Common }{
|
for _, v := range []struct{ uri folio.URI; expected *Common }{
|
||||||
{ uri: "file:///tmp/foo", expected: expectedCommon },
|
{ uri: "file:///tmp/foo", expected: expectedCommon },
|
||||||
}{
|
}{
|
||||||
|
|
||||||
c := NewCommon(FileTypeName, false)
|
c := NewCommon(FileTypeName, false)
|
||||||
c.resourceType = "file"
|
c.resourceType = "file"
|
||||||
assert.Nil(t, c.SetURI(v.uri))
|
assert.Nil(t, c.SetParsedURI(v.uri.Parse()))
|
||||||
assert.Equal(t, v.expected.resourceType , c.resourceType)
|
assert.Equal(t, v.expected.resourceType , c.resourceType)
|
||||||
assert.Equal(t, v.expected.Path, c.Path)
|
assert.Equal(t, v.expected.Path, c.Path)
|
||||||
assert.Equal(t, v.expected.parsedURI.Scheme, c.parsedURI.Scheme)
|
assert.Equal(t, v.expected.parsedURI.Scheme, c.parsedURI.Scheme)
|
||||||
|
@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
_ "gopkg.in/yaml.v3"
|
_ "gopkg.in/yaml.v3"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/url"
|
"net/url"
|
||||||
_ "os"
|
_ "os"
|
||||||
@ -28,6 +28,7 @@ _ "os/exec"
|
|||||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
|
"decl/internal/folio"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -50,7 +51,6 @@ type Container struct {
|
|||||||
stater machine.Stater `yaml:"-" json:"-"`
|
stater machine.Stater `yaml:"-" json:"-"`
|
||||||
Id string `json:"ID,omitempty" yaml:"ID,omitempty"`
|
Id string `json:"ID,omitempty" yaml:"ID,omitempty"`
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
// Path string `json:"path" yaml:"path"`
|
|
||||||
Cmd []string `json:"cmd,omitempty" yaml:"cmd,omitempty"`
|
Cmd []string `json:"cmd,omitempty" yaml:"cmd,omitempty"`
|
||||||
Entrypoint strslice.StrSlice `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"`
|
Entrypoint strslice.StrSlice `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"`
|
||||||
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
|
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
|
||||||
@ -81,21 +81,19 @@ type Container struct {
|
|||||||
NetworkSettings *NetworkSettings
|
NetworkSettings *NetworkSettings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// State string `yaml:"state,omitempty" json:"state,omitempty"`
|
|
||||||
|
|
||||||
// config ConfigurationValueGetter
|
|
||||||
apiClient ContainerClient
|
apiClient ContainerClient
|
||||||
// Resources data.ResourceMapper `json:"-" yaml:"-"`
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ResourceTypes.Register([]string{"container"}, func(u *url.URL) data.Resource {
|
ResourceTypes.Register([]string{"container"}, func(u *url.URL) (c data.Resource) {
|
||||||
c := NewContainer(nil)
|
c = NewContainer(nil)
|
||||||
c.Name = filepath.Join(u.Hostname(), u.Path)
|
if u != nil {
|
||||||
if err := c.Common.SetParsedURI(u); err == nil {
|
if err := folio.CastParsedURI(u).ConstructResource(c); err != nil {
|
||||||
return c
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +112,22 @@ func NewContainer(containerClientApi ContainerClient) *Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) Init(u data.URIParser) error {
|
||||||
|
if u == nil {
|
||||||
|
u = folio.URI(c.URI()).Parse()
|
||||||
|
}
|
||||||
|
uri := u.URL()
|
||||||
|
c.Name = filepath.Join(uri.Hostname(), uri.Path)
|
||||||
|
return c.SetParsedURI(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) SetParsedURI(u data.URIParser) (err error) {
|
||||||
|
if err = c.Common.SetParsedURI(u); err == nil {
|
||||||
|
c.Name = filepath.Join(c.Common.parsedURI.Hostname(), c.Common.parsedURI.Path)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Container) SetResourceMapper(resources data.ResourceMapper) {
|
func (c *Container) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
c.Resources = resources
|
c.Resources = resources
|
||||||
}
|
}
|
||||||
@ -122,7 +136,7 @@ func (c *Container) Clone() data.Resource {
|
|||||||
return &Container {
|
return &Container {
|
||||||
Id: c.Id,
|
Id: c.Id,
|
||||||
Name: c.Name,
|
Name: c.Name,
|
||||||
Common: c.Common,
|
Common: c.Common.Clone(),
|
||||||
Cmd: c.Cmd,
|
Cmd: c.Cmd,
|
||||||
Entrypoint: c.Entrypoint,
|
Entrypoint: c.Entrypoint,
|
||||||
Args: c.Args,
|
Args: c.Args,
|
||||||
@ -146,7 +160,6 @@ func (c *Container) Clone() data.Resource {
|
|||||||
SizeRw: c.SizeRw,
|
SizeRw: c.SizeRw,
|
||||||
SizeRootFs: c.SizeRootFs,
|
SizeRootFs: c.SizeRootFs,
|
||||||
Networks: c.Networks,
|
Networks: c.Networks,
|
||||||
// State: c.State,
|
|
||||||
apiClient: c.apiClient,
|
apiClient: c.apiClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,10 +171,6 @@ func (c *Container) StateMachine() machine.Stater {
|
|||||||
return c.stater
|
return c.stater
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) UseConfig(config data.ConfigurationValueGetter) {
|
|
||||||
c.config = config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) JSON() ([]byte, error) {
|
func (c *Container) JSON() ([]byte, error) {
|
||||||
return json.Marshal(c)
|
return json.Marshal(c)
|
||||||
}
|
}
|
||||||
@ -429,7 +438,7 @@ func (c *Container) Type() string { return "container" }
|
|||||||
func (c *Container) ResolveId(ctx context.Context) string {
|
func (c *Container) ResolveId(ctx context.Context) string {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if err = c.Common.SetURI(c.URI()); err != nil {
|
if err = c.Common.SetParsedURI(folio.URI(c.URI()).Parse()); err != nil {
|
||||||
triggerErr := c.StateMachine().Trigger("notexists")
|
triggerErr := c.StateMachine().Trigger("notexists")
|
||||||
panic(fmt.Errorf("%w: %s %s, %w", err, c.Type(), c.Name, triggerErr))
|
panic(fmt.Errorf("%w: %s %s, %w", err, c.Type(), c.Name, triggerErr))
|
||||||
}
|
}
|
||||||
|
@ -101,11 +101,14 @@ type ContainerImage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"container-image"}, func(u *url.URL) data.Resource {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"container-image"}, func(u *url.URL) (c data.Resource) {
|
||||||
c := NewContainerImage(nil)
|
c = NewContainerImage(nil)
|
||||||
c.Name = ContainerImageNameFromURI(u)
|
if u != nil {
|
||||||
slog.Info("NewContainerImage", "container", c)
|
if err := folio.CastParsedURI(u).ConstructResource(c); err != nil {
|
||||||
return c
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,13 +121,24 @@ func NewContainerImage(containerClientApi ContainerImageClient) *ContainerImage
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &ContainerImage{
|
c := &ContainerImage{
|
||||||
Common: &Common{ includeQueryParamsInURI: true, resourceType: ContainerImageTypeName },
|
Common: NewCommon(ContainerImageTypeName, true),
|
||||||
apiClient: apiClient,
|
apiClient: apiClient,
|
||||||
InjectJX: true,
|
InjectJX: true,
|
||||||
PushImage: false,
|
PushImage: false,
|
||||||
ConverterTypes: folio.DocumentRegistry.ConverterTypes,
|
ConverterTypes: folio.DocumentRegistry.ConverterTypes,
|
||||||
}
|
}
|
||||||
|
c.Common.NormalizePath = c.NormalizePath
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerImage) Init(u data.URIParser) (err error) {
|
||||||
|
if u == nil {
|
||||||
|
u = folio.URI(c.URI()).Parse()
|
||||||
|
}
|
||||||
|
err = c.SetParsedURI(u)
|
||||||
|
c.Name = ContainerImageNameFromURI(u.URL())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ContainerImage) RegistryAuthConfig() (authConfig registry.AuthConfig, err error) {
|
func (c *ContainerImage) RegistryAuthConfig() (authConfig registry.AuthConfig, err error) {
|
||||||
@ -175,6 +189,10 @@ func (c *ContainerImage) RegistryAuth() (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ContainerImage) NormalizePath() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ContainerImage) SetResourceMapper(resources data.ResourceMapper) {
|
func (c *ContainerImage) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
c.Resources = resources
|
c.Resources = resources
|
||||||
}
|
}
|
||||||
@ -249,24 +267,6 @@ func (c *ContainerImage) URI() string {
|
|||||||
return URIFromContainerImageName(c.Name)
|
return URIFromContainerImageName(c.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
func (c *ContainerImage) SetURI(uri string) error {
|
|
||||||
resourceUri, e := url.Parse(uri)
|
|
||||||
if e == nil {
|
|
||||||
if resourceUri.Scheme == c.Type() {
|
|
||||||
c.Name = strings.Join([]string{resourceUri.Hostname(), resourceUri.RequestURI()}, ":")
|
|
||||||
} else {
|
|
||||||
e = fmt.Errorf("%w: %s is not a %s", ErrInvalidResourceURI, uri, c.Type())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerImage) UseConfig(config data.ConfigurationValueGetter) {
|
|
||||||
c.config = config
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (c *ContainerImage) JSON() ([]byte, error) {
|
func (c *ContainerImage) JSON() ([]byte, error) {
|
||||||
return json.Marshal(c)
|
return json.Marshal(c)
|
||||||
}
|
}
|
||||||
@ -789,12 +789,6 @@ func (c *ContainerImage) Delete(ctx context.Context) error {
|
|||||||
|
|
||||||
_, err := c.apiClient.ImageRemove(ctx, c.Id, options)
|
_, err := c.apiClient.ImageRemove(ctx, c.Id, options)
|
||||||
return err
|
return err
|
||||||
/*
|
|
||||||
for _, img := range deletedImages {
|
|
||||||
fmt.Printf("Deleted image: %s\n", img.Deleted)
|
|
||||||
fmt.Printf("Untagged image: %s\n", img.Untagged)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ContainerImage) Type() string { return "container-image" }
|
func (c *ContainerImage) Type() string { return "container-image" }
|
||||||
@ -821,5 +815,3 @@ func (c *ContainerImage) ResolveId(ctx context.Context) string {
|
|||||||
}
|
}
|
||||||
return c.Id
|
return c.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ _ "log/slog"
|
|||||||
"net/url"
|
"net/url"
|
||||||
_ "os"
|
_ "os"
|
||||||
_ "os/exec"
|
_ "os/exec"
|
||||||
"path/filepath"
|
|
||||||
_ "strings"
|
_ "strings"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
@ -39,30 +38,33 @@ type ContainerNetworkClient interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ContainerNetwork struct {
|
type ContainerNetwork struct {
|
||||||
*Common `json:",inline" yaml:",inline"`
|
*Common `json:",inline" yaml:",inline"`
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
Id string `json:"ID,omitempty" yaml:"ID,omitempty"`
|
Id string `json:"ID,omitempty" yaml:"ID,omitempty"`
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
Driver string `json:"driver,omitempty" yaml:"driver,omitempty"`
|
Driver string `json:"driver,omitempty" yaml:"driver,omitempty"`
|
||||||
EnableIPv6 bool `json:"enableipv6,omitempty" yaml:"enableipv6,omitempty"`
|
EnableIPv6 bool `json:"enableipv6,omitempty" yaml:"enableipv6,omitempty"`
|
||||||
Internal bool `json:"internal,omitempty" yaml:"internal,omitempty"`
|
Internal bool `json:"internal,omitempty" yaml:"internal,omitempty"`
|
||||||
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
|
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
|
||||||
Created time.Time `json:"created" yaml:"created"`
|
Created time.Time `json:"created" yaml:"created"`
|
||||||
//State string `yaml:"state"`
|
|
||||||
|
|
||||||
apiClient ContainerNetworkClient
|
apiClient ContainerNetworkClient
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"container-network"}, func(u *url.URL) data.Resource {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"container-network"}, func(u *url.URL) (n data.Resource) {
|
||||||
n := NewContainerNetwork(nil)
|
n = NewContainerNetwork(nil)
|
||||||
n.Name = filepath.Join(u.Hostname(), u.Path)
|
if u != nil {
|
||||||
return n
|
if err := folio.CastParsedURI(u).ConstructResource(n); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContainerNetwork(containerClientApi ContainerNetworkClient) *ContainerNetwork {
|
func NewContainerNetwork(containerClientApi ContainerNetworkClient) (cn *ContainerNetwork) {
|
||||||
var apiClient ContainerNetworkClient = containerClientApi
|
var apiClient ContainerNetworkClient = containerClientApi
|
||||||
if apiClient == nil {
|
if apiClient == nil {
|
||||||
var err error
|
var err error
|
||||||
@ -71,10 +73,23 @@ func NewContainerNetwork(containerClientApi ContainerNetworkClient) *ContainerNe
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &ContainerNetwork{
|
cn = &ContainerNetwork{
|
||||||
Common: &Common{ includeQueryParamsInURI: true, resourceType: ContainerNetworkTypeName },
|
|
||||||
apiClient: apiClient,
|
apiClient: apiClient,
|
||||||
}
|
}
|
||||||
|
cn.Common = NewCommon(ContainerNetworkTypeName, true)
|
||||||
|
cn.Common.NormalizePath = cn.NormalizePath
|
||||||
|
return cn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ContainerNetwork) Init(u data.URIParser) error {
|
||||||
|
if u == nil {
|
||||||
|
u = folio.URI(n.URI()).Parse()
|
||||||
|
}
|
||||||
|
return n.SetParsedURI(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ContainerNetwork) NormalizePath() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *ContainerNetwork) SetResourceMapper(resources data.ResourceMapper) {
|
func (n *ContainerNetwork) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
@ -86,7 +101,6 @@ func (n *ContainerNetwork) Clone() data.Resource {
|
|||||||
Common: n.Common,
|
Common: n.Common,
|
||||||
Id: n.Id,
|
Id: n.Id,
|
||||||
Name: n.Name,
|
Name: n.Name,
|
||||||
//State: n.State,
|
|
||||||
apiClient: n.apiClient,
|
apiClient: n.apiClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,24 +162,6 @@ func (n *ContainerNetwork) URI() string {
|
|||||||
return fmt.Sprintf("container-network://%s", n.Name)
|
return fmt.Sprintf("container-network://%s", n.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
func (n *ContainerNetwork) SetURI(uri string) error {
|
|
||||||
resourceUri, e := url.Parse(uri)
|
|
||||||
if e == nil {
|
|
||||||
if resourceUri.Scheme == n.Type() {
|
|
||||||
n.Name, e = filepath.Abs(filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI()))
|
|
||||||
} else {
|
|
||||||
e = fmt.Errorf("%w: %s is not a %s", ErrInvalidResourceURI, uri, n.Type())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ContainerNetwork) UseConfig(config data.ConfigurationValueGetter) {
|
|
||||||
n.config = config
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (n *ContainerNetwork) JSON() ([]byte, error) {
|
func (n *ContainerNetwork) JSON() ([]byte, error) {
|
||||||
return json.Marshal(n)
|
return json.Marshal(n)
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,22 @@ type Exec struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"exec"}, func(u *url.URL) data.Resource {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"exec"}, func(u *url.URL) (res data.Resource) {
|
||||||
x := NewExec()
|
x := NewExec()
|
||||||
return x
|
res = x
|
||||||
|
if u != nil {
|
||||||
|
uri := folio.CastParsedURI(u)
|
||||||
|
if ri, ok := res.(data.ResourceInitializer); ok {
|
||||||
|
if err := ri.Init(uri); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := x.SetParsedURI(uri); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +69,7 @@ func (x *Exec) SetResourceMapper(resources data.ResourceMapper) {
|
|||||||
|
|
||||||
func (x *Exec) Clone() data.Resource {
|
func (x *Exec) Clone() data.Resource {
|
||||||
return &Exec {
|
return &Exec {
|
||||||
Common: x.Common,
|
Common: x.Common.Clone(),
|
||||||
Id: x.Id,
|
Id: x.Id,
|
||||||
CreateTemplate: x.CreateTemplate,
|
CreateTemplate: x.CreateTemplate,
|
||||||
ReadTemplate: x.ReadTemplate,
|
ReadTemplate: x.ReadTemplate,
|
||||||
@ -76,13 +89,16 @@ 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) (err error) {
|
func (x *Exec) Init(u data.URIParser) (err error) {
|
||||||
err = x.Common.SetURI(uri)
|
if u == nil {
|
||||||
|
u = folio.URI(x.URI()).Parse()
|
||||||
|
}
|
||||||
|
err = x.SetParsedURI(u)
|
||||||
x.Id = x.Common.Path
|
x.Id = x.Common.Path
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) SetParsedURI(uri *url.URL) (err error) {
|
func (x *Exec) SetParsedURI(uri data.URIParser) (err error) {
|
||||||
err = x.Common.SetParsedURI(uri)
|
err = x.Common.SetParsedURI(uri)
|
||||||
x.Id = x.Common.Path
|
x.Id = x.Common.Path
|
||||||
return
|
return
|
||||||
|
@ -4,20 +4,21 @@
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "context"
|
_ "context"
|
||||||
_ "encoding/json"
|
_ "encoding/json"
|
||||||
_ "fmt"
|
_ "fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
_ "gopkg.in/yaml.v3"
|
_ "gopkg.in/yaml.v3"
|
||||||
_ "io"
|
_ "io"
|
||||||
_ "log"
|
_ "log"
|
||||||
_ "net/http"
|
_ "net/http"
|
||||||
_ "net/http/httptest"
|
_ "net/http/httptest"
|
||||||
_ "net/url"
|
_ "net/url"
|
||||||
_ "os"
|
_ "os"
|
||||||
_ "strings"
|
_ "strings"
|
||||||
"testing"
|
"testing"
|
||||||
"decl/internal/command"
|
"decl/internal/command"
|
||||||
|
"decl/internal/folio"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewExecResource(t *testing.T) {
|
func TestNewExecResource(t *testing.T) {
|
||||||
@ -55,9 +56,10 @@ func TestCreateExec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExecSetURI(t *testing.T) {
|
func TestExecSetURI(t *testing.T) {
|
||||||
|
var uri folio.URI = "exec://12345_key"
|
||||||
x := NewExec()
|
x := NewExec()
|
||||||
assert.NotNil(t, x)
|
assert.NotNil(t, x)
|
||||||
e := x.SetURI("exec://" + "12345_key")
|
e := x.SetParsedURI(uri.Parse())
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
assert.Equal(t, "exec", x.Type())
|
assert.Equal(t, "exec", x.Type())
|
||||||
assert.Equal(t, "12345_key", x.Path)
|
assert.Equal(t, "12345_key", x.Path)
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@ -56,19 +55,14 @@ var ErrInvalidFileGroup error = errors.New("Unknown Group")
|
|||||||
type FileMode string
|
type FileMode string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"file"}, func(u *url.URL) data.Resource {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"file"}, func(u *url.URL) (res data.Resource) {
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
f.parsedURI = u
|
slog.Info("FileFactory", "uri", u)
|
||||||
//f.Uri.SetURL(u)
|
if u != nil {
|
||||||
f.Path = filepath.Join(u.Hostname(), u.Path)
|
if err := folio.CastParsedURI(u).ConstructResource(f); err != nil {
|
||||||
f.exttype, f.fileext = f.Uri.Extension()
|
panic(err)
|
||||||
|
}
|
||||||
slog.Info("folio.DocumentRegistry.ResourceTypes.Register()()", "url", u, "file", f)
|
|
||||||
/*
|
|
||||||
if absPath, err := filepath.Abs(f.Path); err == nil {
|
|
||||||
f.Filesystem = os.DirFS(filepath.Dir(absPath))
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
return f
|
return f
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -83,18 +77,12 @@ The `SerializeContent` the flag allows forcing the content to be serialized in t
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
type File struct {
|
type File struct {
|
||||||
Uri folio.URI `json:"uri,omitempty" yaml:"uri,omitempty"`
|
*Common `json:",inline" yaml:",inline"`
|
||||||
parsedURI *url.URL `json:"-" yaml:"-"`
|
|
||||||
Filesystem fs.FS `json:"-" yaml:"-"`
|
Filesystem fs.FS `json:"-" yaml:"-"`
|
||||||
|
|
||||||
exttype string `json:"-" yaml:"-"`
|
|
||||||
fileext string `json:"-" yaml:"-"`
|
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
normalizePath bool `json:"-" yaml:"-"`
|
|
||||||
absPath string `json:"-" yaml:"-"`
|
|
||||||
basePath int `json:"-" yaml:"-"`
|
basePath int `json:"-" yaml:"-"`
|
||||||
|
|
||||||
Path string `json:"path" yaml:"path"`
|
|
||||||
Owner string `json:"owner" yaml:"owner"`
|
Owner string `json:"owner" yaml:"owner"`
|
||||||
Group string `json:"group" yaml:"group"`
|
Group string `json:"group" yaml:"group"`
|
||||||
Mode FileMode `json:"mode" yaml:"mode"`
|
Mode FileMode `json:"mode" yaml:"mode"`
|
||||||
@ -109,10 +97,8 @@ type File struct {
|
|||||||
Size int64 `json:"size,omitempty" yaml:"size,omitempty"`
|
Size int64 `json:"size,omitempty" yaml:"size,omitempty"`
|
||||||
Target string `json:"target,omitempty" yaml:"target,omitempty"`
|
Target string `json:"target,omitempty" yaml:"target,omitempty"`
|
||||||
FileType FileType `json:"filetype" yaml:"filetype"`
|
FileType FileType `json:"filetype" yaml:"filetype"`
|
||||||
State string `json:"state,omitempty" yaml:"state,omitempty"`
|
|
||||||
SerializeContent bool `json:"serializecontent,omitempty" yaml:"serializecontent,omitempty"`
|
SerializeContent bool `json:"serializecontent,omitempty" yaml:"serializecontent,omitempty"`
|
||||||
GzipContent bool `json:"gzipcontent,omitempty" yaml:"gzipcontent,omitempty"`
|
GzipContent bool `json:"gzipcontent,omitempty" yaml:"gzipcontent,omitempty"`
|
||||||
config data.ConfigurationValueGetter
|
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,22 +109,48 @@ type ResourceFileInfo struct {
|
|||||||
func NewFile() *File {
|
func NewFile() *File {
|
||||||
currentUser, _ := user.Current()
|
currentUser, _ := user.Current()
|
||||||
group, _ := user.LookupGroupId(currentUser.Gid)
|
group, _ := user.LookupGroupId(currentUser.Gid)
|
||||||
f := &File{ normalizePath: false, Owner: currentUser.Username, Group: group.Name, Mode: "0644", FileType: RegularFile, SerializeContent: false }
|
f := &File{
|
||||||
|
Common: NewCommon(FileTypeName, true),
|
||||||
|
Owner: currentUser.Username,
|
||||||
|
Group: group.Name,
|
||||||
|
Mode: "0644",
|
||||||
|
FileType: RegularFile,
|
||||||
|
SerializeContent: false,
|
||||||
|
}
|
||||||
|
f.PathNormalization(false)
|
||||||
slog.Info("NewFile()", "file", f)
|
slog.Info("NewFile()", "file", f)
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNormalizedFile() *File {
|
func NewNormalizedFile() *File {
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
f.normalizePath = true
|
f.PathNormalization(true)
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *File) Init(u data.URIParser) error {
|
||||||
|
if u == nil {
|
||||||
|
u = folio.URI(f.URI()).Parse()
|
||||||
|
}
|
||||||
|
return f.SetParsedURI(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) NormalizePath() error {
|
||||||
|
return f.Common.NormalizePath()
|
||||||
|
}
|
||||||
|
|
||||||
func (f *File) ContentType() string {
|
func (f *File) ContentType() string {
|
||||||
|
var ext strings.Builder
|
||||||
if f.parsedURI.Scheme != "file" {
|
if f.parsedURI.Scheme != "file" {
|
||||||
return f.parsedURI.Scheme
|
return f.parsedURI.Scheme
|
||||||
}
|
}
|
||||||
return f.exttype
|
if f.fileext == "" {
|
||||||
|
return f.exttype
|
||||||
|
}
|
||||||
|
ext.WriteString(f.exttype)
|
||||||
|
ext.WriteRune('.')
|
||||||
|
ext.WriteString(f.fileext)
|
||||||
|
return ext.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) SetResourceMapper(resources data.ResourceMapper) {
|
func (f *File) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
@ -147,13 +159,7 @@ func (f *File) SetResourceMapper(resources data.ResourceMapper) {
|
|||||||
|
|
||||||
func (f *File) Clone() data.Resource {
|
func (f *File) Clone() data.Resource {
|
||||||
return &File {
|
return &File {
|
||||||
Uri: f.Uri,
|
Common: f.Common.Clone(),
|
||||||
parsedURI: f.parsedURI,
|
|
||||||
exttype: f.exttype,
|
|
||||||
fileext: f.fileext,
|
|
||||||
normalizePath: f.normalizePath,
|
|
||||||
absPath: f.absPath,
|
|
||||||
Path: f.Path,
|
|
||||||
Owner: f.Owner,
|
Owner: f.Owner,
|
||||||
Group: f.Group,
|
Group: f.Group,
|
||||||
Mode: f.Mode,
|
Mode: f.Mode,
|
||||||
@ -165,7 +171,6 @@ func (f *File) Clone() data.Resource {
|
|||||||
Size: f.Size,
|
Size: f.Size,
|
||||||
Target: f.Target,
|
Target: f.Target,
|
||||||
FileType: f.FileType,
|
FileType: f.FileType,
|
||||||
State: f.State,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,26 +187,26 @@ func (f *File) Notify(m *machine.EventMessage) {
|
|||||||
switch m.On {
|
switch m.On {
|
||||||
case machine.ENTERSTATEEVENT:
|
case machine.ENTERSTATEEVENT:
|
||||||
switch m.Dest {
|
switch m.Dest {
|
||||||
case "start_stat":
|
case "start_stat":
|
||||||
if statErr := f.ReadStat(); statErr == nil {
|
if statErr := f.ReadStat(); statErr == nil {
|
||||||
if triggerErr := f.StateMachine().Trigger("exists"); triggerErr == nil {
|
if triggerErr := f.StateMachine().Trigger("exists"); triggerErr == nil {
|
||||||
return
|
return
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if triggerErr := f.StateMachine().Trigger("notexists"); triggerErr == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case "start_read":
|
|
||||||
if _,readErr := f.Read(ctx); readErr == nil {
|
|
||||||
if triggerErr := f.StateMachine().Trigger("state_read"); triggerErr == nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
f.State = "absent"
|
|
||||||
panic(triggerErr)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
f.State = "absent"
|
if triggerErr := f.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "start_read":
|
||||||
|
if _,readErr := f.Read(ctx); readErr == nil {
|
||||||
|
if triggerErr := f.StateMachine().Trigger("state_read"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
f.Common.State = "absent"
|
||||||
|
panic(triggerErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f.Common.State = "absent"
|
||||||
if ! errors.Is(readErr, ErrResourceStateAbsent) {
|
if ! errors.Is(readErr, ErrResourceStateAbsent) {
|
||||||
panic(readErr)
|
panic(readErr)
|
||||||
}
|
}
|
||||||
@ -212,25 +217,36 @@ func (f *File) Notify(m *machine.EventMessage) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
f.State = "absent"
|
f.Common.State = "absent"
|
||||||
panic(e)
|
panic(e)
|
||||||
}
|
}
|
||||||
|
case "start_update":
|
||||||
|
if updateErr := f.Update(ctx); updateErr == nil {
|
||||||
|
if triggerErr := f.stater.Trigger("updated"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
f.Common.State = "absent"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f.Common.State = "absent"
|
||||||
|
panic(updateErr)
|
||||||
|
}
|
||||||
case "start_delete":
|
case "start_delete":
|
||||||
if deleteErr := f.Delete(ctx); deleteErr == nil {
|
if deleteErr := f.Delete(ctx); deleteErr == nil {
|
||||||
if triggerErr := f.StateMachine().Trigger("deleted"); triggerErr == nil {
|
if triggerErr := f.StateMachine().Trigger("deleted"); triggerErr == nil {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
f.State = "present"
|
f.Common.State = "present"
|
||||||
panic(triggerErr)
|
panic(triggerErr)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
f.State = "present"
|
f.Common.State = "present"
|
||||||
panic(deleteErr)
|
panic(deleteErr)
|
||||||
}
|
}
|
||||||
case "absent":
|
case "absent":
|
||||||
f.State = "absent"
|
f.Common.State = "absent"
|
||||||
case "present", "created", "read":
|
case "present", "created", "read":
|
||||||
f.State = "present"
|
f.Common.State = "present"
|
||||||
}
|
}
|
||||||
case machine.EXITSTATEEVENT:
|
case machine.EXITSTATEEVENT:
|
||||||
}
|
}
|
||||||
@ -240,12 +256,8 @@ func (f *File) SetGzipContent(flag bool) {
|
|||||||
f.GzipContent = flag
|
f.GzipContent = flag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) PathNormalization(flag bool) {
|
|
||||||
f.normalizePath = flag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) FilePath() string {
|
func (f *File) FilePath() string {
|
||||||
return f.Path
|
return f.Common.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) SetFS(fsys fs.FS) {
|
func (f *File) SetFS(fsys fs.FS) {
|
||||||
@ -253,63 +265,23 @@ func (f *File) SetFS(fsys fs.FS) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) URI() string {
|
func (f *File) URI() string {
|
||||||
return fmt.Sprintf("file://%s", f.Path)
|
return fmt.Sprintf("file://%s", f.Common.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) RelativePath() string {
|
func (f *File) RelativePath() string {
|
||||||
return f.Path[f.basePath:]
|
return f.Common.Path[f.basePath:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) SetBasePath(index int) {
|
func (f *File) SetBasePath(index int) {
|
||||||
if index < len(f.Path) {
|
if index < len(f.Common.Path) {
|
||||||
f.basePath = index
|
f.basePath = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) SetURI(uri string) (err error) {
|
|
||||||
slog.Info("File.SetURI()", "uri", uri, "file", f, "parsed", f.parsedURI)
|
|
||||||
f.SetURIFromString(uri)
|
|
||||||
err = f.SetParsedURI(f.Uri.Parse())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) DetectGzip() bool {
|
func (f *File) DetectGzip() bool {
|
||||||
return (f.parsedURI.Query().Get("gzip") == "true" || f.fileext == "gz" || f.exttype == "tgz" || f.exttype == "gz" || f.fileext == "tgz" )
|
return (f.parsedURI.Query().Get("gzip") == "true" || f.fileext == "gz" || f.exttype == "tgz" || f.exttype == "gz" || f.fileext == "tgz" )
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) SetURIFromString(uri string) {
|
|
||||||
f.Uri = folio.URI(uri)
|
|
||||||
f.exttype, f.fileext = f.Uri.Extension()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) SetParsedURI(u *url.URL) (err error) {
|
|
||||||
if u != nil {
|
|
||||||
if u.Scheme == "" {
|
|
||||||
u.Scheme = "file"
|
|
||||||
f.Uri = ""
|
|
||||||
}
|
|
||||||
if f.Uri.IsEmpty() {
|
|
||||||
f.SetURIFromString(u.String())
|
|
||||||
}
|
|
||||||
slog.Info("File.SetParsedURI()", "parsed", u, "path", f.Path)
|
|
||||||
f.parsedURI = u
|
|
||||||
if f.parsedURI.Scheme == "file" {
|
|
||||||
f.Path = filepath.Join(f.parsedURI.Hostname(), f.parsedURI.Path)
|
|
||||||
slog.Info("File.SetParsedURI()", "path", f.Path)
|
|
||||||
if err = f.NormalizePath(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = fmt.Errorf("%w: %s is not a file", ErrInvalidResourceURI, f.Uri)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) UseConfig(config data.ConfigurationValueGetter) {
|
|
||||||
f.config = config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) JSON() ([]byte, error) {
|
func (f *File) JSON() ([]byte, error) {
|
||||||
return json.Marshal(f)
|
return json.Marshal(f)
|
||||||
}
|
}
|
||||||
@ -325,7 +297,7 @@ func (f *File) Validate() (err error) {
|
|||||||
|
|
||||||
func (f *File) Apply() error {
|
func (f *File) Apply() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
switch f.State {
|
switch f.Common.State {
|
||||||
case "absent":
|
case "absent":
|
||||||
return f.Delete(ctx)
|
return f.Delete(ctx)
|
||||||
case "present":
|
case "present":
|
||||||
@ -367,9 +339,10 @@ func (f *File) ResolveId(ctx context.Context) string {
|
|||||||
if e := f.NormalizePath(); e != nil {
|
if e := f.NormalizePath(); e != nil {
|
||||||
panic(e)
|
panic(e)
|
||||||
}
|
}
|
||||||
return f.Path
|
return f.Common.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func (f *File) NormalizePath() (err error) {
|
func (f *File) NormalizePath() (err error) {
|
||||||
if f.config != nil {
|
if f.config != nil {
|
||||||
if prefixPath, configErr := f.config.GetValue("prefix"); configErr == nil {
|
if prefixPath, configErr := f.config.GetValue("prefix"); configErr == nil {
|
||||||
@ -381,6 +354,7 @@ func (f *File) NormalizePath() (err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (f *File) GetContentSourceRef() string {
|
func (f *File) GetContentSourceRef() string {
|
||||||
return string(f.ContentSourceRef)
|
return string(f.ContentSourceRef)
|
||||||
@ -393,7 +367,7 @@ func (f *File) SetContentSourceRef(uri string) {
|
|||||||
|
|
||||||
func (f *File) Stat() (info fs.FileInfo, err error) {
|
func (f *File) Stat() (info fs.FileInfo, err error) {
|
||||||
if _, ok := f.Filesystem.(embed.FS); ok {
|
if _, ok := f.Filesystem.(embed.FS); ok {
|
||||||
info, err = fs.Stat(f.Filesystem, f.Path)
|
info, err = fs.Stat(f.Filesystem, f.Common.Path)
|
||||||
} else {
|
} else {
|
||||||
info, err = os.Lstat(f.absPath)
|
info, err = os.Lstat(f.absPath)
|
||||||
}
|
}
|
||||||
@ -453,12 +427,12 @@ func (f *File) Create(ctx context.Context) error {
|
|||||||
|
|
||||||
switch f.FileType {
|
switch f.FileType {
|
||||||
case SymbolicLinkFile:
|
case SymbolicLinkFile:
|
||||||
linkErr := os.Symlink(f.Target, f.Path)
|
linkErr := os.Symlink(f.Target, f.Common.Path)
|
||||||
if linkErr != nil {
|
if linkErr != nil {
|
||||||
return linkErr
|
return linkErr
|
||||||
}
|
}
|
||||||
case DirectoryFile:
|
case DirectoryFile:
|
||||||
if mkdirErr := os.MkdirAll(f.Path, mode); mkdirErr != nil {
|
if mkdirErr := os.MkdirAll(f.Common.Path, mode); mkdirErr != nil {
|
||||||
return mkdirErr
|
return mkdirErr
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -486,7 +460,8 @@ func (f *File) Create(ctx context.Context) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
var createdFileWriter io.WriteCloser
|
var createdFileWriter io.WriteCloser
|
||||||
createdFile, fileErr := os.Create(f.Path)
|
createdFile, fileErr := os.Create(f.Common.Path)
|
||||||
|
slog.Info("File.Create(): os.Create()", "path", f.Common.Path, "error", fileErr)
|
||||||
if fileErr != nil {
|
if fileErr != nil {
|
||||||
return fileErr
|
return fileErr
|
||||||
}
|
}
|
||||||
@ -500,6 +475,7 @@ func (f *File) Create(ctx context.Context) error {
|
|||||||
|
|
||||||
defer createdFile.Close()
|
defer createdFile.Close()
|
||||||
|
|
||||||
|
slog.Info("File.Create(): Chmod()", "path", f.Common.Path, "mode", mode)
|
||||||
if chmodErr := createdFile.Chmod(mode); chmodErr != nil {
|
if chmodErr := createdFile.Chmod(mode); chmodErr != nil {
|
||||||
return chmodErr
|
return chmodErr
|
||||||
}
|
}
|
||||||
@ -511,16 +487,20 @@ func (f *File) Create(ctx context.Context) error {
|
|||||||
|
|
||||||
f.Sha256 = fmt.Sprintf("%x", hash.Sum(nil))
|
f.Sha256 = fmt.Sprintf("%x", hash.Sum(nil))
|
||||||
if !f.Mtime.IsZero() && !f.Atime.IsZero() {
|
if !f.Mtime.IsZero() && !f.Atime.IsZero() {
|
||||||
if chtimesErr := os.Chtimes(f.Path, f.Atime, f.Mtime); chtimesErr != nil {
|
slog.Info("File.Create(): Chtimes()", "path", f.Common.Path, "atime", f.Atime, "mtime", f.Mtime)
|
||||||
|
if chtimesErr := os.Chtimes(f.Common.Path, f.Atime, f.Mtime); chtimesErr != nil {
|
||||||
return chtimesErr
|
return chtimesErr
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
slog.Info("File.Create(): Chtimes() SKIPPED", "path", f.Common.Path, "atime", f.Atime, "mtime", f.Mtime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
slog.Info("File.Create(): Chown()", "path", f.Common.Path, "uid", uid, "gid", gid)
|
||||||
|
|
||||||
if chownErr := os.Chown(f.Path, uid, gid); chownErr != nil {
|
if chownErr := os.Chown(f.Common.Path, uid, gid); chownErr != nil {
|
||||||
return chownErr
|
return chownErr
|
||||||
}
|
}
|
||||||
f.State = "present"
|
f.Common.State = "present"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,7 +509,7 @@ func (f *File) Update(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Delete(ctx context.Context) error {
|
func (f *File) Delete(ctx context.Context) error {
|
||||||
return os.Remove(f.Path)
|
return os.Remove(f.Common.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) UpdateContentAttributes() {
|
func (f *File) UpdateContentAttributes() {
|
||||||
@ -585,11 +565,11 @@ func (f *File) ContentSourceRefStat() (info fs.FileInfo) {
|
|||||||
|
|
||||||
func (f *File) ReadStat() (err error) {
|
func (f *File) ReadStat() (err error) {
|
||||||
var info fs.FileInfo
|
var info fs.FileInfo
|
||||||
slog.Info("ReadStat()", "filesystem", f.Filesystem, "path", f.Path)
|
slog.Info("ReadStat()", "filesystem", f.Filesystem, "path", f.Common.Path)
|
||||||
|
|
||||||
info, err = f.Stat()
|
info, err = f.Stat()
|
||||||
|
|
||||||
slog.Info("ReadStat()", "filesystem", f.Filesystem, "path", f.Path, "info", info, "error", err)
|
slog.Info("ReadStat()", "filesystem", f.Filesystem, "path", f.Common.Path, "info", info, "error", err)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_ = f.SetFileInfo(info)
|
_ = f.SetFileInfo(info)
|
||||||
@ -603,9 +583,9 @@ func (f *File) ReadStat() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
slog.Info("ReadStat()", "stat", info, "path", f.Path)
|
slog.Info("ReadStat()", "stat", info, "path", f.Common.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.State = "absent"
|
f.Common.State = "absent"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -613,27 +593,29 @@ func (f *File) ReadStat() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) open() (file io.ReadCloser, err error) {
|
func (f *File) open() (file io.ReadCloser, err error) {
|
||||||
slog.Info("open()", "file", f.Path, "fs", f.Filesystem)
|
slog.Info("open()", "file", f.Common.Path, "fs", f.Filesystem)
|
||||||
if _, ok := f.Filesystem.(embed.FS); ok {
|
if _, ok := f.Filesystem.(embed.FS); ok {
|
||||||
file, err = f.Filesystem.Open(f.Path)
|
file, err = f.Filesystem.Open(f.Common.Path)
|
||||||
} else {
|
} else {
|
||||||
file, err = os.Open(f.Path)
|
file, err = os.Open(f.Common.Path)
|
||||||
}
|
}
|
||||||
if f.GzipContent && f.DetectGzip() {
|
if f.GzipContent && f.DetectGzip() {
|
||||||
file, err = gzip.NewReader(file)
|
file, err = gzip.NewReader(file)
|
||||||
}
|
}
|
||||||
slog.Info("open()", "file", f.Path, "error", err)
|
slog.Info("open()", "file", f.Common.Path, "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Read(ctx context.Context) ([]byte, error) {
|
func (f *File) Read(ctx context.Context) ([]byte, error) {
|
||||||
|
/*
|
||||||
if normalizePathErr := f.NormalizePath(); normalizePathErr != nil {
|
if normalizePathErr := f.NormalizePath(); normalizePathErr != nil {
|
||||||
return nil, normalizePathErr
|
return nil, normalizePathErr
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
statErr := f.ReadStat()
|
statErr := f.ReadStat()
|
||||||
if statErr != nil {
|
if statErr != nil {
|
||||||
return nil, fmt.Errorf("%w - %w", ErrResourceStateAbsent, statErr)
|
return nil, fmt.Errorf("%w - %w: %s", ErrResourceStateAbsent, statErr, f.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch f.FileType {
|
switch f.FileType {
|
||||||
@ -653,13 +635,13 @@ func (f *File) Read(ctx context.Context) ([]byte, error) {
|
|||||||
f.Sha256 = fmt.Sprintf("%x", sha256.Sum256(fileContent))
|
f.Sha256 = fmt.Sprintf("%x", sha256.Sum256(fileContent))
|
||||||
}
|
}
|
||||||
case SymbolicLinkFile:
|
case SymbolicLinkFile:
|
||||||
linkTarget, pathErr := os.Readlink(f.Path)
|
linkTarget, pathErr := os.Readlink(f.Common.Path)
|
||||||
if pathErr != nil {
|
if pathErr != nil {
|
||||||
return nil, pathErr
|
return nil, pathErr
|
||||||
}
|
}
|
||||||
f.Target = linkTarget
|
f.Target = linkTarget
|
||||||
}
|
}
|
||||||
f.State = "present"
|
f.Common.State = "present"
|
||||||
return yaml.Marshal(f)
|
return yaml.Marshal(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,15 +4,15 @@ package resource
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "encoding/json"
|
_ "encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"io"
|
"io"
|
||||||
_ "log"
|
_ "log"
|
||||||
_ "net/http"
|
_ "net/http"
|
||||||
_ "net/http/httptest"
|
_ "net/http/httptest"
|
||||||
_ "net/url"
|
_ "net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -23,6 +23,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
|
"decl/internal/folio"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,7 +38,8 @@ func TestNewFileNormalized(t *testing.T) {
|
|||||||
|
|
||||||
f := NewNormalizedFile()
|
f := NewNormalizedFile()
|
||||||
assert.NotNil(t, f)
|
assert.NotNil(t, f)
|
||||||
assert.Nil(t, f.SetURI("file://" + indirectFile))
|
f.Path = indirectFile
|
||||||
|
assert.Nil(t, f.Init(nil))
|
||||||
|
|
||||||
assert.NotEqual(t, indirectFile, f.Path)
|
assert.NotEqual(t, indirectFile, f.Path)
|
||||||
assert.Equal(t, absFilePath, f.Path)
|
assert.Equal(t, absFilePath, f.Path)
|
||||||
@ -84,13 +86,21 @@ func TestReadFile(t *testing.T) {
|
|||||||
testFile := NewFile()
|
testFile := NewFile()
|
||||||
e := testFile.LoadDecl(decl)
|
e := testFile.LoadDecl(decl)
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
|
|
||||||
|
assert.Equal(t, "present", testFile.Common.State)
|
||||||
|
assert.Equal(t, file, testFile.Common.Path)
|
||||||
|
|
||||||
applyErr := testFile.Apply()
|
applyErr := testFile.Apply()
|
||||||
assert.Nil(t, applyErr)
|
assert.Nil(t, applyErr)
|
||||||
|
|
||||||
|
assert.FileExists(t, file)
|
||||||
|
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
assert.NotEqual(t, nil, f)
|
assert.NotNil(t, f)
|
||||||
|
|
||||||
f.Path = file
|
f.Path = file
|
||||||
|
assert.Nil(t, f.Init(nil))
|
||||||
|
|
||||||
r, e := f.Read(ctx)
|
r, e := f.Read(ctx)
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
assert.Equal(t, ProcessTestUserName, f.Owner)
|
assert.Equal(t, ProcessTestUserName, f.Owner)
|
||||||
@ -118,7 +128,8 @@ func TestUseConfig(t *testing.T) {
|
|||||||
return nil, data.ErrUnknownConfigurationKey
|
return nil, data.ErrUnknownConfigurationKey
|
||||||
}))
|
}))
|
||||||
|
|
||||||
assert.Nil(t, f.SetURI(fmt.Sprintf("file://%s", file)))
|
uri := folio.URI(fmt.Sprintf("file://%s", file))
|
||||||
|
assert.Nil(t, f.Init(uri.Parse()))
|
||||||
assert.Equal(t, filepath.Join("/tmp", file), f.FilePath())
|
assert.Equal(t, filepath.Join("/tmp", file), f.FilePath())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,8 +274,8 @@ func TestFileSetURI(t *testing.T) {
|
|||||||
file, _ := filepath.Abs(TempDir.FilePath("testuri.txt"))
|
file, _ := filepath.Abs(TempDir.FilePath("testuri.txt"))
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
assert.NotNil(t, f)
|
assert.NotNil(t, f)
|
||||||
e := f.SetURI("file://" + file)
|
uri := folio.URI("file://" + file).Parse()
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, f.Init(uri))
|
||||||
assert.Equal(t, "file", f.Type())
|
assert.Equal(t, "file", f.Type())
|
||||||
assert.Equal(t, file, f.Path)
|
assert.Equal(t, file, f.Path)
|
||||||
}
|
}
|
||||||
@ -302,6 +313,7 @@ func TestFileUpdateAttributesFromFileInfo(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileReadStat(t *testing.T) {
|
func TestFileReadStat(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
link := TempDir.FilePath("link.txt")
|
link := TempDir.FilePath("link.txt")
|
||||||
linkTargetFile := TempDir.FilePath("testuri.txt")
|
linkTargetFile := TempDir.FilePath("testuri.txt")
|
||||||
|
|
||||||
@ -309,8 +321,8 @@ func TestFileReadStat(t *testing.T) {
|
|||||||
assert.NotNil(t, f)
|
assert.NotNil(t, f)
|
||||||
|
|
||||||
f.Path = linkTargetFile
|
f.Path = linkTargetFile
|
||||||
e := f.NormalizePath()
|
f.PathNormalization(true)
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, f.Init(nil))
|
||||||
|
|
||||||
statErr := f.ReadStat()
|
statErr := f.ReadStat()
|
||||||
assert.Error(t, statErr)
|
assert.Error(t, statErr)
|
||||||
@ -324,8 +336,9 @@ func TestFileReadStat(t *testing.T) {
|
|||||||
|
|
||||||
l := NewFile()
|
l := NewFile()
|
||||||
assert.NotNil(t, l)
|
assert.NotNil(t, l)
|
||||||
|
l.PathNormalization(true)
|
||||||
|
assert.Nil(t, l.Init(nil))
|
||||||
|
|
||||||
assert.Nil(t, l.NormalizePath())
|
|
||||||
l.FileType = SymbolicLinkFile
|
l.FileType = SymbolicLinkFile
|
||||||
l.Path = link
|
l.Path = link
|
||||||
l.Target = linkTargetFile
|
l.Target = linkTargetFile
|
||||||
@ -340,6 +353,9 @@ func TestFileReadStat(t *testing.T) {
|
|||||||
|
|
||||||
testRead := NewFile()
|
testRead := NewFile()
|
||||||
testRead.Path = link
|
testRead.Path = link
|
||||||
|
|
||||||
|
assert.Nil(t, testRead.Init(nil))
|
||||||
|
|
||||||
_,testReadErr := testRead.Read(ctx)
|
_,testReadErr := testRead.Read(ctx)
|
||||||
assert.Nil(t, testReadErr)
|
assert.Nil(t, testReadErr)
|
||||||
assert.Equal(t, linkTargetFile, testRead.Target)
|
assert.Equal(t, linkTargetFile, testRead.Target)
|
||||||
@ -355,6 +371,8 @@ func TestFileResourceFileInfo(t *testing.T) {
|
|||||||
f.Mode = "0600"
|
f.Mode = "0600"
|
||||||
f.Content = "some test data"
|
f.Content = "some test data"
|
||||||
f.State = "present"
|
f.State = "present"
|
||||||
|
assert.Nil(t, f.Init(nil))
|
||||||
|
|
||||||
assert.Nil(t, f.Apply())
|
assert.Nil(t, f.Apply())
|
||||||
|
|
||||||
_, readErr := f.Read(context.Background())
|
_, readErr := f.Read(context.Background())
|
||||||
@ -378,20 +396,45 @@ func TestFileClone(t *testing.T) {
|
|||||||
assert.NotNil(t, f)
|
assert.NotNil(t, f)
|
||||||
|
|
||||||
f.Path = testFile
|
f.Path = testFile
|
||||||
|
assert.Nil(t, f.Init(nil))
|
||||||
f.Mode = "0600"
|
f.Mode = "0600"
|
||||||
f.State = "present"
|
f.State = "present"
|
||||||
assert.Nil(t, f.Apply())
|
assert.Nil(t, f.Apply())
|
||||||
|
|
||||||
|
origin := time.Now()
|
||||||
|
|
||||||
_,readErr := f.Read(ctx)
|
_,readErr := f.Read(ctx)
|
||||||
assert.Nil(t, readErr)
|
assert.Nil(t, readErr)
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
assert.Greater(t, origin, f.Mtime)
|
||||||
|
|
||||||
clone := f.Clone().(*File)
|
clone := f.Clone().(*File)
|
||||||
assert.Equal(t, f, clone)
|
assert.Equal(t, f.Common.Path, clone.Common.Path)
|
||||||
clone.Mtime = time.Time{}
|
assert.Equal(t, f.Common.absPath, clone.Common.absPath)
|
||||||
|
assert.Equal(t, f.Common.parsedURI, clone.Common.parsedURI)
|
||||||
|
assert.Equal(t, f.Common.exttype, clone.Common.exttype)
|
||||||
|
assert.Equal(t, f.Common.fileext, clone.Common.fileext)
|
||||||
|
assert.Equal(t, f.Common.State, clone.Common.State)
|
||||||
|
assert.Equal(t, f.Size, clone.Size)
|
||||||
|
assert.Equal(t, f.Owner, clone.Owner)
|
||||||
|
assert.Equal(t, f.Group, clone.Group)
|
||||||
|
assert.Equal(t, f.Mode, clone.Mode)
|
||||||
|
assert.Equal(t, f.Atime, clone.Atime)
|
||||||
|
assert.Equal(t, f.Mtime, clone.Mtime)
|
||||||
|
assert.Equal(t, f.Ctime, clone.Ctime)
|
||||||
|
assert.Equal(t, f.Content, clone.Content)
|
||||||
|
assert.Equal(t, f.Sha256, clone.Sha256)
|
||||||
|
|
||||||
|
clone.Mtime = time.Now()
|
||||||
clone.Path = testCloneFile
|
clone.Path = testCloneFile
|
||||||
|
assert.Nil(t, clone.Init(nil))
|
||||||
|
|
||||||
|
assert.NotEqual(t, f.absPath, clone.absPath)
|
||||||
|
|
||||||
|
slog.Info("TestFileClone", "clone", clone)
|
||||||
assert.Nil(t, clone.Apply())
|
assert.Nil(t, clone.Apply())
|
||||||
|
slog.Info("TestFileClone - applied mtime change", "clone", clone)
|
||||||
|
|
||||||
_,updateReadErr := f.Read(ctx)
|
_,updateReadErr := f.Read(ctx)
|
||||||
assert.Nil(t, updateReadErr)
|
assert.Nil(t, updateReadErr)
|
||||||
@ -399,7 +442,8 @@ func TestFileClone(t *testing.T) {
|
|||||||
_, cloneReadErr := clone.Read(ctx)
|
_, cloneReadErr := clone.Read(ctx)
|
||||||
assert.Nil(t, cloneReadErr)
|
assert.Nil(t, cloneReadErr)
|
||||||
|
|
||||||
fmt.Printf("file %#v\nclone %#v\n", f, clone)
|
slog.Info("TestFileClone - read mtime change", "orig", f.Mtime, "clone", clone.Mtime)
|
||||||
|
fmt.Printf("file %#v\n %#v\nclone %#v\n %#v\n", f, f.Common, clone, clone.Common)
|
||||||
assert.NotEqual(t, f.Mtime, clone.Mtime)
|
assert.NotEqual(t, f.Mtime, clone.Mtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,12 +456,16 @@ func TestFileErrors(t *testing.T) {
|
|||||||
stater := f.StateMachine()
|
stater := f.StateMachine()
|
||||||
|
|
||||||
f.Path = testFile
|
f.Path = testFile
|
||||||
|
assert.Nil(t, f.Init(nil))
|
||||||
f.Mode = "631"
|
f.Mode = "631"
|
||||||
assert.Nil(t, stater.Trigger("create"))
|
assert.Nil(t, stater.Trigger("create"))
|
||||||
|
|
||||||
|
assert.FileExists(t, f.Path)
|
||||||
|
|
||||||
read := NewFile()
|
read := NewFile()
|
||||||
readStater := read.StateMachine()
|
readStater := read.StateMachine()
|
||||||
read.Path = testFile
|
read.Path = testFile
|
||||||
|
assert.Nil(t, read.Init(nil))
|
||||||
assert.Nil(t, readStater.Trigger("read"))
|
assert.Nil(t, readStater.Trigger("read"))
|
||||||
assert.Equal(t, FileMode("0631"), read.Mode)
|
assert.Equal(t, FileMode("0631"), read.Mode)
|
||||||
|
|
||||||
@ -534,7 +582,8 @@ func TestFilePathURI(t *testing.T) {
|
|||||||
e := f.LoadDecl(decl)
|
e := f.LoadDecl(decl)
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
assert.Equal(t, "", f.FilePath())
|
assert.Equal(t, "", f.FilePath())
|
||||||
assert.ErrorContains(t, f.Validate(), "path: String length must be greater than or equal to 1")
|
// assert.ErrorContains(t, f.Validate(), "path: String length must be greater than or equal to 1")
|
||||||
|
assert.ErrorContains(t, f.Validate(), "path is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileAbsent(t *testing.T) {
|
func TestFileAbsent(t *testing.T) {
|
||||||
@ -593,8 +642,8 @@ func TestFileSetURIError(t *testing.T) {
|
|||||||
file := TempDir.FilePath("fooread.txt")
|
file := TempDir.FilePath("fooread.txt")
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
assert.NotNil(t, f)
|
assert.NotNil(t, f)
|
||||||
e := f.SetURI("foo://" + file)
|
uri := folio.URI("foo://" + file).Parse()
|
||||||
assert.NotNil(t, e)
|
e := f.Init(uri)
|
||||||
assert.ErrorIs(t, e, ErrInvalidResourceURI)
|
assert.ErrorIs(t, e, ErrInvalidResourceURI)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -602,8 +651,8 @@ func TestFileContentType(t *testing.T) {
|
|||||||
file := TempDir.FilePath("fooread.txt")
|
file := TempDir.FilePath("fooread.txt")
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
assert.NotNil(t, f)
|
assert.NotNil(t, f)
|
||||||
e := f.SetURI("file://" + file)
|
uri := folio.URI("file://" + file).Parse()
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, f.Init(uri))
|
||||||
assert.Equal(t, "txt", f.ContentType())
|
assert.Equal(t, "txt", f.ContentType())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,9 @@ type decodeGroup Group
|
|||||||
type GroupType string
|
type GroupType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
GroupTypeAddGroup = "addgroup"
|
GroupTypeName TypeName = "group"
|
||||||
GroupTypeGroupAdd = "groupadd"
|
GroupTypeAddGroup GroupType = "addgroup"
|
||||||
|
GroupTypeGroupAdd GroupType = "groupadd"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUnsupportedGroupType error = errors.New("The GroupType is not supported on this system")
|
var ErrUnsupportedGroupType error = errors.New("The GroupType is not supported on this system")
|
||||||
@ -47,28 +48,26 @@ type Group struct {
|
|||||||
ReadCommand *command.Command `json:"-" yaml:"-"`
|
ReadCommand *command.Command `json:"-" yaml:"-"`
|
||||||
UpdateCommand *command.Command `json:"-" yaml:"-"`
|
UpdateCommand *command.Command `json:"-" yaml:"-"`
|
||||||
DeleteCommand *command.Command `json:"-" yaml:"-"`
|
DeleteCommand *command.Command `json:"-" yaml:"-"`
|
||||||
State string `json:"state,omitempty" yaml:"state,omitempty"`
|
|
||||||
config data.ConfigurationValueGetter
|
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||||
|
groupStatus *user.Group `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGroup() *Group {
|
func NewGroup() (g *Group) {
|
||||||
return &Group{}
|
g = &Group{}
|
||||||
|
g.Common = NewCommon(GroupTypeName, true)
|
||||||
|
g.Common.NormalizePath = g.NormalizePath
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"group"}, func(u *url.URL) data.Resource {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"group"}, func(u *url.URL) (group data.Resource) {
|
||||||
group := NewGroup()
|
group = NewGroup()
|
||||||
group.Name = u.Hostname()
|
if u != nil {
|
||||||
group.GID = LookupGIDString(u.Hostname())
|
if err := folio.CastParsedURI(u).ConstructResource(group); err != nil {
|
||||||
if _, addGroupPathErr := exec.LookPath("addgroup"); addGroupPathErr == nil {
|
panic(err)
|
||||||
group.GroupType = GroupTypeAddGroup
|
}
|
||||||
}
|
}
|
||||||
if _, pathErr := exec.LookPath("groupadd"); pathErr == nil {
|
return
|
||||||
group.GroupType = GroupTypeGroupAdd
|
|
||||||
}
|
|
||||||
group.CreateCommand, group.ReadCommand, group.UpdateCommand, group.DeleteCommand = group.GroupType.NewCRUD()
|
|
||||||
return group
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,15 +81,36 @@ func FindSystemGroupType() GroupType {
|
|||||||
return GroupTypeAddGroup
|
return GroupTypeAddGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Group) Init(u data.URIParser) error {
|
||||||
|
if u == nil {
|
||||||
|
u = folio.URI(g.URI()).Parse()
|
||||||
|
}
|
||||||
|
uri := u.URL()
|
||||||
|
g.Name = uri.Hostname()
|
||||||
|
g.GID = LookupGIDString(uri.Hostname())
|
||||||
|
if _, addGroupPathErr := exec.LookPath("addgroup"); addGroupPathErr == nil {
|
||||||
|
g.GroupType = GroupTypeAddGroup
|
||||||
|
}
|
||||||
|
if _, pathErr := exec.LookPath("groupadd"); pathErr == nil {
|
||||||
|
g.GroupType = GroupTypeGroupAdd
|
||||||
|
}
|
||||||
|
g.CreateCommand, g.ReadCommand, g.UpdateCommand, g.DeleteCommand = g.GroupType.NewCRUD()
|
||||||
|
return g.SetParsedURI(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) NormalizePath() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Group) SetResourceMapper(resources data.ResourceMapper) {
|
func (g *Group) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
g.Resources = resources
|
g.Resources = resources
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Clone() data.Resource {
|
func (g *Group) Clone() data.Resource {
|
||||||
newg := &Group {
|
newg := &Group {
|
||||||
|
Common: g.Common,
|
||||||
Name: g.Name,
|
Name: g.Name,
|
||||||
GID: g.GID,
|
GID: g.GID,
|
||||||
State: g.State,
|
|
||||||
GroupType: g.GroupType,
|
GroupType: g.GroupType,
|
||||||
}
|
}
|
||||||
newg.CreateCommand, newg.ReadCommand, newg.UpdateCommand, newg.DeleteCommand = g.GroupType.NewCRUD()
|
newg.CreateCommand, newg.ReadCommand, newg.UpdateCommand, newg.DeleteCommand = g.GroupType.NewCRUD()
|
||||||
@ -109,40 +129,62 @@ func (g *Group) Notify(m *machine.EventMessage) {
|
|||||||
switch m.On {
|
switch m.On {
|
||||||
case machine.ENTERSTATEEVENT:
|
case machine.ENTERSTATEEVENT:
|
||||||
switch m.Dest {
|
switch m.Dest {
|
||||||
|
case "start_stat":
|
||||||
|
if statErr := g.ReadStat(); statErr == nil {
|
||||||
|
if triggerErr := g.StateMachine().Trigger("exists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if triggerErr := g.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "start_read":
|
||||||
|
if _,readErr := g.Read(ctx); readErr == nil {
|
||||||
|
if triggerErr := g.StateMachine().Trigger("state_read"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
g.Common.State = "absent"
|
||||||
|
panic(triggerErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g.Common.State = "absent"
|
||||||
|
panic(readErr)
|
||||||
|
}
|
||||||
case "start_create":
|
case "start_create":
|
||||||
if e := g.Create(ctx); e == nil {
|
if e := g.Create(ctx); e == nil {
|
||||||
if triggerErr := g.stater.Trigger("created"); triggerErr == nil {
|
if triggerErr := g.stater.Trigger("created"); triggerErr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.State = "absent"
|
g.Common.State = "absent"
|
||||||
case "present":
|
|
||||||
g.State = "present"
|
|
||||||
|
case "start_update":
|
||||||
|
if updateErr := g.Update(ctx); updateErr == nil {
|
||||||
|
if triggerErr := g.stater.Trigger("updated"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
g.Common.State = "absent"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g.Common.State = "absent"
|
||||||
|
panic(updateErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "absent":
|
||||||
|
g.Common.State = "absent"
|
||||||
|
case "present", "created", "read":
|
||||||
|
g.Common.State = "present"
|
||||||
}
|
}
|
||||||
case machine.EXITSTATEEVENT:
|
case machine.EXITSTATEEVENT:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) SetURI(uri string) error {
|
|
||||||
resourceUri, e := url.Parse(uri)
|
|
||||||
if e == nil {
|
|
||||||
if resourceUri.Scheme == "group" {
|
|
||||||
g.Name = resourceUri.Hostname()
|
|
||||||
} else {
|
|
||||||
e = fmt.Errorf("%w: %s is not a group", ErrInvalidResourceURI, uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Group) URI() string {
|
func (g *Group) URI() string {
|
||||||
return fmt.Sprintf("group://%s", g.Name)
|
return fmt.Sprintf("group://%s", g.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) UseConfig(config data.ConfigurationValueGetter) {
|
|
||||||
g.config = config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Group) ResolveId(ctx context.Context) string {
|
func (g *Group) ResolveId(ctx context.Context) string {
|
||||||
return LookupUIDString(g.Name)
|
return LookupUIDString(g.Name)
|
||||||
}
|
}
|
||||||
@ -153,7 +195,7 @@ func (g *Group) Validate() error {
|
|||||||
|
|
||||||
func (g *Group) Apply() error {
|
func (g *Group) Apply() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
switch g.State {
|
switch g.Common.State {
|
||||||
case "present":
|
case "present":
|
||||||
_, NoGroupExists := LookupGID(g.Name)
|
_, NoGroupExists := LookupGID(g.Name)
|
||||||
if NoGroupExists != nil {
|
if NoGroupExists != nil {
|
||||||
@ -198,10 +240,26 @@ func (g *Group) Create(ctx context.Context) (error) {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Group) ReadStat() (err error) {
|
||||||
|
if g.groupStatus == nil {
|
||||||
|
if g.groupStatus, err = user.LookupGroup(g.Name); err != nil {
|
||||||
|
g.Common.State = "absent"
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(g.groupStatus.Gid) < 1 {
|
||||||
|
g.Common.State = "absent"
|
||||||
|
return ErrResourceStateAbsent
|
||||||
|
}
|
||||||
|
g.GID = g.groupStatus.Gid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func (g *Group) Read(ctx context.Context) ([]byte, error) {
|
func (g *Group) Read(ctx context.Context) ([]byte, error) {
|
||||||
exErr := g.ReadCommand.Extractor(nil, g)
|
exErr := g.ReadCommand.Extractor(nil, g)
|
||||||
if exErr != nil {
|
if exErr != nil {
|
||||||
g.State = "absent"
|
g.Common.State = "absent"
|
||||||
}
|
}
|
||||||
if yaml, yamlErr := yaml.Marshal(g); yamlErr != nil {
|
if yaml, yamlErr := yaml.Marshal(g); yamlErr != nil {
|
||||||
return yaml, yamlErr
|
return yaml, yamlErr
|
||||||
@ -210,8 +268,9 @@ func (g *Group) Read(ctx context.Context) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Update(ctx context.Context) (error) {
|
func (g *Group) Update(ctx context.Context) (err error) {
|
||||||
return g.Create(ctx)
|
_, err = g.UpdateCommand.Execute(g)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Delete(ctx context.Context) (error) {
|
func (g *Group) Delete(ctx context.Context) (error) {
|
||||||
@ -345,7 +404,7 @@ func NewGroupReadCommand() *command.Command {
|
|||||||
c := command.NewCommand()
|
c := command.NewCommand()
|
||||||
c.Extractor = func(out []byte, target any) error {
|
c.Extractor = func(out []byte, target any) error {
|
||||||
g := target.(*Group)
|
g := target.(*Group)
|
||||||
g.State = "absent"
|
g.Common.State = "absent"
|
||||||
var readGroup *user.Group
|
var readGroup *user.Group
|
||||||
var e error
|
var e error
|
||||||
if g.Name != "" {
|
if g.Name != "" {
|
||||||
@ -360,7 +419,7 @@ func NewGroupReadCommand() *command.Command {
|
|||||||
g.Name = readGroup.Name
|
g.Name = readGroup.Name
|
||||||
g.GID = readGroup.Gid
|
g.GID = readGroup.Gid
|
||||||
if g.GID != "" {
|
if g.GID != "" {
|
||||||
g.State = "present"
|
g.Common.State = "present"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
@ -369,7 +428,17 @@ func NewGroupReadCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewGroupUpdateCommand() *command.Command {
|
func NewGroupUpdateCommand() *command.Command {
|
||||||
return nil
|
c := command.NewCommand()
|
||||||
|
c.Path = "addgroup"
|
||||||
|
c.FailOnError = false
|
||||||
|
c.Args = []command.CommandArg{
|
||||||
|
command.CommandArg("{{ if .GID }}-g {{ .GID }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ .Name }}"),
|
||||||
|
}
|
||||||
|
c.Extractor = func(out []byte, target any) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGroupDelDeleteCommand() *command.Command {
|
func NewGroupDelDeleteCommand() *command.Command {
|
||||||
@ -416,7 +485,7 @@ func NewReadGroupsCommand() *command.Command {
|
|||||||
g := (*Groups)[lineIndex]
|
g := (*Groups)[lineIndex]
|
||||||
g.Name = groupRecord[0]
|
g.Name = groupRecord[0]
|
||||||
g.GID = groupRecord[2]
|
g.GID = groupRecord[2]
|
||||||
g.State = "present"
|
g.Common.State = "present"
|
||||||
g.GroupType = SystemGroupType
|
g.GroupType = SystemGroupType
|
||||||
lineIndex++
|
lineIndex++
|
||||||
}
|
}
|
||||||
|
@ -60,16 +60,11 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func HTTPFactory(u *url.URL) data.Resource {
|
func HTTPFactory(u *url.URL) data.Resource {
|
||||||
var err error
|
|
||||||
h := NewHTTP()
|
h := NewHTTP()
|
||||||
|
if u != nil {
|
||||||
slog.Info("HTTP.Factory", "http", h, "url", u)
|
if err := folio.CastParsedURI(u).ConstructResource(h); err != nil {
|
||||||
if err = h.SetParsedURI(u); err != nil {
|
panic(err)
|
||||||
panic(err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err = h.Open(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
@ -102,17 +97,36 @@ type HTTP struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP() *HTTP {
|
func NewHTTP() *HTTP {
|
||||||
h := &HTTP{ client: &http.Client{}, Common: &Common{ includeQueryParamsInURI: true, resourceType: HTTPTypeName, SchemeCheck: func(scheme string) bool {
|
h := &HTTP{ client: &http.Client{} }
|
||||||
switch scheme {
|
h.Common = NewCommon(HTTPTypeName, true)
|
||||||
case "http", "https":
|
h.Common.SchemeCheck = h.SchemeCheck
|
||||||
return true
|
h.Common.NormalizePath = h.NormalizePath
|
||||||
}
|
|
||||||
return false
|
|
||||||
} } }
|
|
||||||
slog.Info("NewHTTP()", "http", h)
|
slog.Info("NewHTTP()", "http", h)
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *HTTP) SchemeCheck(scheme string) bool {
|
||||||
|
switch scheme {
|
||||||
|
case "http", "https":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HTTP) Init(u data.URIParser) (err error) {
|
||||||
|
if u == nil {
|
||||||
|
u = folio.URI(h.URI()).Parse()
|
||||||
|
}
|
||||||
|
if err = h.SetParsedURI(u); err == nil {
|
||||||
|
err = h.Open()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HTTP) NormalizePath() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *HTTP) SetResourceMapper(resources data.ResourceMapper) {
|
func (h *HTTP) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
h.Resources = resources
|
h.Resources = resources
|
||||||
}
|
}
|
||||||
@ -219,16 +233,9 @@ func (h *HTTP) URI() string {
|
|||||||
return h.Endpoint.String()
|
return h.Endpoint.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTP) SetURI(uri string) (err error) {
|
func (h *HTTP) SetParsedURI(u data.URIParser) (err error) {
|
||||||
if err = h.Common.SetURI(uri); err == nil {
|
|
||||||
h.Endpoint = h.Common.Uri
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *HTTP) SetParsedURI(u *url.URL) (err error) {
|
|
||||||
if err = h.Common.SetParsedURI(u); err == nil {
|
if err = h.Common.SetParsedURI(u); err == nil {
|
||||||
h.Endpoint = h.Common.Uri
|
h.Endpoint = h.Common.URI()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -266,11 +273,13 @@ func (h *HTTP) ContentSourceRefStat() (info fs.FileInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTP) ReadStat() (err error) {
|
func (h *HTTP) ReadStat() (err error) {
|
||||||
|
|
||||||
if h.reader == nil {
|
if h.reader == nil {
|
||||||
if err = h.OpenGetter(); err != nil {
|
if err = h.OpenGetter(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var info fs.FileInfo
|
var info fs.FileInfo
|
||||||
info, err = h.reader.Stat()
|
info, err = h.reader.Stat()
|
||||||
|
|
||||||
@ -332,14 +341,14 @@ func (h *HTTP) Apply() error {
|
|||||||
|
|
||||||
func (h *HTTP) Load(docData []byte, f codec.Format) (err error) {
|
func (h *HTTP) Load(docData []byte, f codec.Format) (err error) {
|
||||||
if err = f.StringDecoder(string(docData)).Decode(h); err == nil {
|
if err = f.StringDecoder(string(docData)).Decode(h); err == nil {
|
||||||
err = h.Common.SetURI(string(h.Endpoint))
|
err = h.Common.SetParsedURI(folio.URI(h.Endpoint).Parse())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTP) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
|
func (h *HTTP) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
|
||||||
if err = f.Decoder(r).Decode(h); err == nil {
|
if err = f.Decoder(r).Decode(h); err == nil {
|
||||||
err = h.Common.SetURI(string(h.Endpoint))
|
err = h.Common.SetParsedURI(folio.URI(h.Endpoint).Parse())
|
||||||
//err = h.setParsedURI(h.Endpoint)
|
//err = h.setParsedURI(h.Endpoint)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -347,7 +356,7 @@ func (h *HTTP) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
|
|||||||
|
|
||||||
func (h *HTTP) LoadString(docData string, f codec.Format) (err error) {
|
func (h *HTTP) LoadString(docData string, f codec.Format) (err error) {
|
||||||
if err = f.StringDecoder(docData).Decode(h); err == nil {
|
if err = f.StringDecoder(docData).Decode(h); err == nil {
|
||||||
err = h.Common.SetURI(string(h.Endpoint))
|
err = h.Common.SetParsedURI(folio.URI(h.Endpoint).Parse())
|
||||||
//err = h.setParsedURI(h.Endpoint)
|
//err = h.setParsedURI(h.Endpoint)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -358,7 +367,7 @@ func (h *HTTP) LoadDecl(yamlResourceDeclaration string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTP) ResolveId(ctx context.Context) string {
|
func (h *HTTP) ResolveId(ctx context.Context) string {
|
||||||
_ = h.Common.SetURI(h.Endpoint.String())
|
_ = h.Common.SetParsedURI(folio.URI(h.Endpoint).Parse())
|
||||||
slog.Info("HTTP.ResolveId()", "uri", h.Endpoint.String())
|
slog.Info("HTTP.ResolveId()", "uri", h.Endpoint.String())
|
||||||
return h.Endpoint.String()
|
return h.Endpoint.String()
|
||||||
}
|
}
|
||||||
|
@ -30,20 +30,11 @@ const (
|
|||||||
func init() {
|
func init() {
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"iptable"}, func(u *url.URL) data.Resource {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"iptable"}, func(u *url.URL) data.Resource {
|
||||||
i := NewIptable()
|
i := NewIptable()
|
||||||
i.Table = IptableName(u.Hostname())
|
if u != nil {
|
||||||
if len(u.Path) > 0 {
|
if err := folio.CastParsedURI(u).ConstructResource(i); err != nil {
|
||||||
fields := strings.FieldsFunc(u.Path, func(c rune) bool { return c == '/' })
|
panic(err)
|
||||||
slog.Info("iptables factory", "iptable", i, "uri", u, "fields", fields, "number_fields", len(fields))
|
|
||||||
if len(fields) > 0 {
|
|
||||||
i.Chain = IptableChain(fields[0])
|
|
||||||
if len(fields) < 3 {
|
|
||||||
i.ResourceType = IptableTypeChain
|
|
||||||
} else {
|
|
||||||
i.ResourceType = IptableTypeRule
|
|
||||||
id, _ := strconv.ParseUint(fields[1], 10, 32)
|
|
||||||
i.Id = uint(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD()
|
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD()
|
||||||
}
|
}
|
||||||
return i
|
return i
|
||||||
@ -72,9 +63,9 @@ var IptableNumber = regexp.MustCompile(`^[0-9]+$`)
|
|||||||
type IptableChain string
|
type IptableChain string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
IptableChainInput = "INPUT"
|
IptableChainInput = "INPUT"
|
||||||
IptableChainOutput = "OUTPUT"
|
IptableChainOutput = "OUTPUT"
|
||||||
IptableChainForward = "FORWARD"
|
IptableChainForward = "FORWARD"
|
||||||
IptableChainPreRouting = "PREROUTING"
|
IptableChainPreRouting = "PREROUTING"
|
||||||
IptableChainPostRouting = "POSTROUTING"
|
IptableChainPostRouting = "POSTROUTING"
|
||||||
)
|
)
|
||||||
@ -101,6 +92,8 @@ type ExtensionFlag struct {
|
|||||||
Value string `json:"value" yaml:"value"`
|
Value string `json:"value" yaml:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TargetFlag ExtensionFlag
|
||||||
|
|
||||||
type IptablePort uint16
|
type IptablePort uint16
|
||||||
|
|
||||||
type IptableType string
|
type IptableType string
|
||||||
@ -110,6 +103,8 @@ const (
|
|||||||
IptableTypeChain = "chain"
|
IptableTypeChain = "chain"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type IptableRule string
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidIptableName error = errors.New("The IptableName is not a valid table")
|
ErrInvalidIptableName error = errors.New("The IptableName is not a valid table")
|
||||||
)
|
)
|
||||||
@ -117,32 +112,32 @@ var (
|
|||||||
// Manage the state of iptables rules
|
// Manage the state of iptables rules
|
||||||
// iptable://filter/INPUT/0
|
// iptable://filter/INPUT/0
|
||||||
type Iptable struct {
|
type Iptable struct {
|
||||||
*Common `json:",inline" yaml:",inline"`
|
*Common `json:",inline" yaml:",inline"`
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
parsedURI *url.URL `json:"-" yaml:"-"`
|
Id uint `json:"id,omitempty" yaml:"id,omitempty"`
|
||||||
Id uint `json:"id,omitempty" yaml:"id,omitempty"`
|
Table IptableName `json:"table" yaml:"table"`
|
||||||
Table IptableName `json:"table" yaml:"table"`
|
Chain IptableChain `json:"chain" yaml:"chain"`
|
||||||
Chain IptableChain `json:"chain" yaml:"chain"`
|
Destination IptableCIDR `json:"destination,omitempty" yaml:"destination,omitempty"`
|
||||||
Destination IptableCIDR `json:"destination,omitempty" yaml:"destination,omitempty"`
|
Source IptableCIDR `json:"source,omitempty" yaml:"source,omitempty"`
|
||||||
Source IptableCIDR `json:"source,omitempty" yaml:"source,omitempty"`
|
Dport IptablePort `json:"dport,omitempty" yaml:"dport,omitempty"`
|
||||||
Dport IptablePort `json:"dport,omitempty" yaml:"dport,omitempty"`
|
Sport IptablePort `json:"sport,omitempty" yaml:"sport,omitempty"`
|
||||||
Sport IptablePort `json:"sport,omitempty" yaml:"sport,omitempty"`
|
In string `json:"in,omitempty" yaml:"in,omitempty"`
|
||||||
In string `json:"in,omitempty" yaml:"in,omitempty"`
|
Out string `json:"out,omitempty" yaml:"out,omitempty"`
|
||||||
Out string `json:"out,omitempty" yaml:"out,omitempty"`
|
Match []string `json:"match,omitempty" yaml:"match,omitempty"`
|
||||||
Match []string `json:"match,omitempty" yaml:"match,omitempty"`
|
Flags []ExtensionFlag `json:"extension_flags,omitempty" yaml:"extension_flags,omitempty"`
|
||||||
Flags []ExtensionFlag `json:"extension_flags,omitempty" yaml:"extension_flags,omitempty"`
|
Proto IptableProto `json:"proto,omitempty" yaml:"proto,omitempty"`
|
||||||
Proto IptableProto `json:"proto,omitempty" yaml:"proto,omitempty"`
|
Jump string `json:"jump,omitempty" yaml:"jump,omitempty"`
|
||||||
Jump string `json:"jump,omitempty" yaml:"jump,omitempty"`
|
TargetFlags []TargetFlag `json:"target_flags,omitempty" yaml:"target_flags,omitempty"`
|
||||||
ChainLength uint `json:"-" yaml:"-"`
|
ChainLength uint `json:"-" yaml:"-"`
|
||||||
|
|
||||||
ResourceType IptableType `json:"resourcetype,omitempty" yaml:"resourcetype,omitempty"`
|
ResourceType IptableType `json:"resourcetype,omitempty" yaml:"resourcetype,omitempty"`
|
||||||
CreateCommand *command.Command `yaml:"-" json:"-"`
|
CreateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
ReadCommand *command.Command `yaml:"-" json:"-"`
|
ReadCommand *command.Command `yaml:"-" json:"-"`
|
||||||
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
ReadChainCommand *command.Command `yaml:"-" json:"-"`
|
||||||
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
|
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
||||||
|
|
||||||
config data.ConfigurationValueGetter
|
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
||||||
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -155,10 +150,39 @@ func (n IptableName) Validate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIptable() *Iptable {
|
func NewIptable() (i *Iptable) {
|
||||||
i := &Iptable{ ResourceType: IptableTypeRule, Common: &Common{ resourceType: IptableTypeName } }
|
i = &Iptable{ ResourceType: IptableTypeRule }
|
||||||
|
i.Common = NewCommon(IptableTypeName, false)
|
||||||
|
i.Common.NormalizePath = i.NormalizePath
|
||||||
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD()
|
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD()
|
||||||
return i
|
i.ReadChainCommand = NewIptableChainReadCommand()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iptable) Init(u data.URIParser) (err error) {
|
||||||
|
if u == nil {
|
||||||
|
u = folio.URI(i.URI()).Parse()
|
||||||
|
}
|
||||||
|
uri := u.URL()
|
||||||
|
err = i.SetParsedURI(u)
|
||||||
|
|
||||||
|
i.Table = IptableName(uri.Hostname())
|
||||||
|
if len(uri.Path) > 0 {
|
||||||
|
fields := strings.FieldsFunc(uri.Path, func(c rune) bool { return c == '/' })
|
||||||
|
slog.Info("iptables factory", "iptable", i, "uri", uri, "fields", fields, "number_fields", len(fields))
|
||||||
|
if len(fields) > 0 {
|
||||||
|
i.Chain = IptableChain(fields[0])
|
||||||
|
if len(fields) < 3 {
|
||||||
|
i.ResourceType = IptableTypeChain
|
||||||
|
} else {
|
||||||
|
i.ResourceType = IptableTypeRule
|
||||||
|
id, _ := strconv.ParseUint(fields[1], 10, 32)
|
||||||
|
i.SetId(uint(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD()
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) SetResourceMapper(resources data.ResourceMapper) {
|
func (i *Iptable) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
@ -195,51 +219,119 @@ func (i *Iptable) Notify(m *machine.EventMessage) {
|
|||||||
switch m.On {
|
switch m.On {
|
||||||
case machine.ENTERSTATEEVENT:
|
case machine.ENTERSTATEEVENT:
|
||||||
switch m.Dest {
|
switch m.Dest {
|
||||||
case "start_create":
|
case "start_stat":
|
||||||
if e := i.Create(ctx); e == nil {
|
if statErr := i.ReadStat(ctx); statErr == nil {
|
||||||
if triggerErr := i.stater.Trigger("created"); triggerErr == nil {
|
if triggerErr := i.StateMachine().Trigger("exists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if triggerErr := i.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i.Common.State = "absent"
|
case "start_read":
|
||||||
case "present":
|
if _,readErr := i.Read(ctx); readErr == nil {
|
||||||
|
if triggerErr := i.stater.Trigger("state_read"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
i.Common.State = "absent"
|
||||||
|
panic(triggerErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i.Common.State = "absent"
|
||||||
|
panic(readErr)
|
||||||
|
}
|
||||||
|
case "start_create":
|
||||||
|
if createErr := i.Create(ctx); createErr == nil {
|
||||||
|
if triggerErr := i.stater.Trigger("created"); triggerErr == nil {
|
||||||
|
slog.Info("ContainerImage.Notify()", "created", i, "error", triggerErr)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
slog.Info("ContainerImage.Notify()", "created", i, "error", triggerErr)
|
||||||
|
i.Common.State = "absent"
|
||||||
|
panic(triggerErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i.Common.State = "absent"
|
||||||
|
panic(createErr)
|
||||||
|
}
|
||||||
|
case "start_update":
|
||||||
|
if createErr := i.Update(ctx); createErr == nil {
|
||||||
|
if triggerErr := i.stater.Trigger("updated"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
i.Common.State = "absent"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i.Common.State = "absent"
|
||||||
|
panic(createErr)
|
||||||
|
}
|
||||||
|
case "start_delete":
|
||||||
|
if deleteErr := i.Delete(ctx); deleteErr == nil {
|
||||||
|
if triggerErr := i.stater.Trigger("deleted"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic(deleteErr)
|
||||||
|
}
|
||||||
|
case "present", "created", "read":
|
||||||
i.Common.State = "present"
|
i.Common.State = "present"
|
||||||
|
case "absent":
|
||||||
|
i.Common.State = "absent"
|
||||||
}
|
}
|
||||||
case machine.EXITSTATEEVENT:
|
case machine.EXITSTATEEVENT:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the chain ID and update the mapped URI
|
||||||
|
func (i *Iptable) SetId(id uint) {
|
||||||
|
if i.Id != id {
|
||||||
|
uri := i.URI()
|
||||||
|
i.Id = id
|
||||||
|
decl, ok := i.Resources.Get(uri)
|
||||||
|
if ok {
|
||||||
|
i.Resources.Delete(uri)
|
||||||
|
}
|
||||||
|
i.Resources.Set(i.URI(), decl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Iptable) URI() string {
|
func (i *Iptable) URI() string {
|
||||||
return fmt.Sprintf("iptable://%s/%s/%d", i.Table, i.Chain, i.Id)
|
return fmt.Sprintf("iptable://%s/%s/%d", i.Table, i.Chain, i.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) SetURI(uri string) (err error) {
|
|
||||||
i.parsedURI, err = url.Parse(uri)
|
func (i *Iptable) SetParsedURI(uri data.URIParser) (err error) {
|
||||||
if err == nil {
|
if err = i.Common.SetParsedURI(uri); err == nil {
|
||||||
fields := strings.FieldsFunc(i.parsedURI.Path, func(c rune) bool { return c == '/' })
|
err = i.setFieldsFromPath()
|
||||||
fieldsLen := len(fields)
|
|
||||||
if i.parsedURI.Scheme == "iptable" && fieldsLen > 0 {
|
|
||||||
i.Table = IptableName(i.parsedURI.Hostname())
|
|
||||||
if err = i.Table.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i.Chain = IptableChain(fields[0])
|
|
||||||
if fieldsLen < 2 {
|
|
||||||
i.ResourceType = IptableTypeChain
|
|
||||||
} else {
|
|
||||||
i.ResourceType = IptableTypeRule
|
|
||||||
id, _ := strconv.ParseUint(fields[1], 10, 32)
|
|
||||||
i.Id = uint(id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("%w: %s is not an iptable rule", ErrInvalidResourceURI, uri)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) UseConfig(config data.ConfigurationValueGetter) {
|
func (i *Iptable) NormalizePath() error {
|
||||||
i.config = config
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iptable) setFieldsFromPath() (err error) {
|
||||||
|
fields := strings.FieldsFunc(i.Common.Path, func(c rune) bool { return c == '/' })
|
||||||
|
fieldsLen := len(fields)
|
||||||
|
if fieldsLen > 0 {
|
||||||
|
i.Table = IptableName(fields[0])
|
||||||
|
if err = i.Table.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Chain = IptableChain(fields[1])
|
||||||
|
if fieldsLen < 3 {
|
||||||
|
i.ResourceType = IptableTypeChain
|
||||||
|
} else {
|
||||||
|
i.ResourceType = IptableTypeRule
|
||||||
|
id, _ := strconv.ParseUint(fields[1], 10, 32)
|
||||||
|
i.Id = uint(id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%w: %s is not an iptable rule", ErrInvalidResourceURI, i.Common.URI())
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) Validate() error {
|
func (i *Iptable) Validate() error {
|
||||||
@ -316,6 +408,46 @@ func (i *Iptable) ResolveId(ctx context.Context) string {
|
|||||||
return fmt.Sprintf("%d", i.Id)
|
return fmt.Sprintf("%d", i.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *ExtensionFlag) Match(name string, value string) bool {
|
||||||
|
start := 0
|
||||||
|
if name[1] == '-' {
|
||||||
|
start = 2
|
||||||
|
} else if name[0] == '-' {
|
||||||
|
start = 1
|
||||||
|
}
|
||||||
|
return f.Name == name[start:] && f.Value == value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iptable) HasExtensionFlag(name string) bool {
|
||||||
|
optName := strings.Trim(name, "-")
|
||||||
|
for _, ext := range i.Flags {
|
||||||
|
if ext.Name == optName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iptable) HasTargetFlag(name string) bool {
|
||||||
|
optName := strings.Trim(name, "-")
|
||||||
|
for _, ext := range i.TargetFlags {
|
||||||
|
if ext.Name == optName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TargetFlag) Match(name string, value string) bool {
|
||||||
|
start := 0
|
||||||
|
if name[1] == '-' {
|
||||||
|
start = 2
|
||||||
|
} else if name[0] == '-' {
|
||||||
|
start = 1
|
||||||
|
}
|
||||||
|
return f.Name == name[start:] && f.Value == value
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Iptable) SetFlagValue(opt, value string) bool {
|
func (i *Iptable) SetFlagValue(opt, value string) bool {
|
||||||
switch opt {
|
switch opt {
|
||||||
case "-i":
|
case "-i":
|
||||||
@ -353,10 +485,14 @@ func (i *Iptable) SetFlagValue(opt, value string) bool {
|
|||||||
i.Sport = IptablePort(port)
|
i.Sport = IptablePort(port)
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
if opt[0] == '-' {
|
if opt[0] == '-' {
|
||||||
i.Flags = append(i.Flags, ExtensionFlag{ Name: strings.Trim(opt, "-"), Value: strings.TrimSpace(value)})
|
if len(i.Jump) > 0 {
|
||||||
|
i.TargetFlags = append(i.TargetFlags, TargetFlag{ Name: strings.Trim(opt, "-"), Value: strings.TrimSpace(value)})
|
||||||
|
} else {
|
||||||
|
i.Flags = append(i.Flags, ExtensionFlag{ Name: strings.Trim(opt, "-"), Value: strings.TrimSpace(value)})
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -382,9 +518,15 @@ func (i *Iptable) GetFlagValue(opt string) any {
|
|||||||
case "--sport":
|
case "--sport":
|
||||||
return strconv.Itoa(int(i.Sport))
|
return strconv.Itoa(int(i.Sport))
|
||||||
default:
|
default:
|
||||||
if opt[0] == '-' {
|
if opt[0] == '-' {
|
||||||
return i.Flags
|
slog.Info("Iptable.GetFlagValue()", "opt", opt, "iptable", i)
|
||||||
}
|
if i.HasExtensionFlag(opt) {
|
||||||
|
return i.Flags
|
||||||
|
}
|
||||||
|
if i.HasTargetFlag(opt) {
|
||||||
|
return i.TargetFlags
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -407,9 +549,11 @@ func (i *Iptable) SetRule(flags []string) (assigned bool) {
|
|||||||
|
|
||||||
func (i *Iptable) MatchRule(flags []string) (match bool) {
|
func (i *Iptable) MatchRule(flags []string) (match bool) {
|
||||||
match = true
|
match = true
|
||||||
|
next:
|
||||||
for index, flag := range flags {
|
for index, flag := range flags {
|
||||||
if flag[0] == '-' {
|
if flag[0] == '-' {
|
||||||
value := flags[index + 1]
|
value := flags[index + 1]
|
||||||
|
slog.Info("Iptable.MatchRule()", "flag", flag, "value", value)
|
||||||
switch v := i.GetFlagValue(flag).(type) {
|
switch v := i.GetFlagValue(flag).(type) {
|
||||||
case []string:
|
case []string:
|
||||||
for _,element := range v {
|
for _,element := range v {
|
||||||
@ -417,48 +561,57 @@ func (i *Iptable) MatchRule(flags []string) (match bool) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
match = false
|
match = false
|
||||||
case []ExtensionFlag:
|
case []ExtensionFlag:
|
||||||
for _,element := range v {
|
for _,element := range v {
|
||||||
if element.Name == flag && element.Value == value {
|
if element.Match(flag, value) {
|
||||||
continue
|
continue next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
|
match = false
|
||||||
|
case []TargetFlag:
|
||||||
|
for _,element := range v {
|
||||||
|
if element.Match(flag, value) {
|
||||||
|
continue next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
match = false
|
match = false
|
||||||
case IptableCIDR:
|
case IptableCIDR:
|
||||||
if v == IptableCIDR(value) {
|
if v == IptableCIDR(value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
match = false
|
match = false
|
||||||
case IptableName:
|
case IptableName:
|
||||||
if v == IptableName(value) {
|
if v == IptableName(value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
match = false
|
match = false
|
||||||
case IptableChain:
|
case IptableChain:
|
||||||
if v == IptableChain(value) {
|
if v == IptableChain(value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
match = false
|
match = false
|
||||||
default:
|
default:
|
||||||
if v.(string) == value {
|
if v.(string) == value {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
match = false
|
match = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule()", "flags", flags, "match", match)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) ReadChainLength() error {
|
func (i *Iptable) ReadChainLength() error {
|
||||||
c := command.NewCommand()
|
output,err := i.ReadChainCommand.Execute(i)
|
||||||
c.Path = "iptables"
|
|
||||||
c.Args = []command.CommandArg{
|
|
||||||
command.CommandArg("-S"),
|
|
||||||
command.CommandArg("{{ .Chain }}"),
|
|
||||||
}
|
|
||||||
output,err := c.Execute(i)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
linesCount := strings.Count(string(output), "\n")
|
linesCount := strings.Count(string(output), "\n")
|
||||||
if linesCount > 0 {
|
if linesCount > 0 {
|
||||||
@ -470,13 +623,16 @@ func (i *Iptable) ReadChainLength() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) Create(ctx context.Context) error {
|
func (i *Iptable) Create(ctx context.Context) (err error) {
|
||||||
|
slog.Info("Iptable.Create()", "iptable", i)
|
||||||
if i.Id > 0 {
|
if i.Id > 0 {
|
||||||
if lenErr := i.ReadChainLength(); lenErr != nil {
|
if lenErr := i.ReadChainLength(); lenErr != nil {
|
||||||
return lenErr
|
return lenErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err := i.CreateCommand.Execute(i)
|
|
||||||
|
_, err = i.CreateCommand.Execute(i)
|
||||||
|
slog.Info("Iptable.Create()", "err", err, "iptable", i, "createcommand", i.CreateCommand)
|
||||||
//slog.Info("IptableChain Create()", "err", err, "errstr", err.Error(), "iptable", i, "createcommand", i.CreateCommand)
|
//slog.Info("IptableChain Create()", "err", err, "errstr", err.Error(), "iptable", i, "createcommand", i.CreateCommand)
|
||||||
// TODO add Command status/error handler rather than using the read extractor
|
// TODO add Command status/error handler rather than using the read extractor
|
||||||
if i.CreateCommand.Extractor != nil {
|
if i.CreateCommand.Extractor != nil {
|
||||||
@ -484,7 +640,20 @@ func (i *Iptable) Create(ctx context.Context) error {
|
|||||||
return i.CreateCommand.Extractor([]byte(err.Error()), i)
|
return i.CreateCommand.Extractor([]byte(err.Error()), i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iptable) ReadStat(ctx context.Context) (err error) {
|
||||||
|
if i.ReadCommand.Exists() {
|
||||||
|
var out []byte
|
||||||
|
if out, err = i.ReadCommand.Execute(i); err == nil {
|
||||||
|
err = i.ReadCommand.Extractor(out, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i.Id == 0 {
|
||||||
|
return ErrResourceStateAbsent
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) Read(ctx context.Context) ([]byte, error) {
|
func (i *Iptable) Read(ctx context.Context) ([]byte, error) {
|
||||||
@ -503,8 +672,15 @@ func (i *Iptable) Update(ctx context.Context) error {
|
|||||||
return i.Create(ctx)
|
return i.Create(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) Delete(ctx context.Context) error {
|
func (i *Iptable) Delete(ctx context.Context) (err error) {
|
||||||
return nil
|
if i.Id < 1 {
|
||||||
|
return fmt.Errorf("Failed to find rule to delete")
|
||||||
|
}
|
||||||
|
var out []byte
|
||||||
|
if out, err = i.DeleteCommand.Execute(i); err == nil {
|
||||||
|
err = i.DeleteCommand.Extractor(out, i)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) Type() string { return "iptable" }
|
func (i *Iptable) Type() string { return "iptable" }
|
||||||
@ -552,9 +728,7 @@ func NewIptableCreateCommand() *command.Command {
|
|||||||
c.Args = []command.CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
command.CommandArg("-t"),
|
command.CommandArg("-t"),
|
||||||
command.CommandArg("{{ .Table }}"),
|
command.CommandArg("{{ .Table }}"),
|
||||||
command.CommandArg("{{ if le .Id .ChainLength }}-R{{ else }}-A{{ end }}"),
|
command.CommandArg("{{ if and (le .Id .ChainLength) (gt .Id 0) }}-R {{ .Chain }} {{ .Id }}{{ else }}-A {{ .Chain }}{{ end }}"),
|
||||||
command.CommandArg("{{ .Chain }}"),
|
|
||||||
command.CommandArg("{{ if le .Id .ChainLength }}{{ .Id }}{{ end }}"),
|
|
||||||
command.CommandArg("{{ if .In }}-i {{ .In }}{{ else if .Out }}-o {{ .Out }}{{ end }}"),
|
command.CommandArg("{{ if .In }}-i {{ .In }}{{ else if .Out }}-o {{ .Out }}{{ end }}"),
|
||||||
command.CommandArg("{{ range .Match }}-m {{ . }} {{- end }}"),
|
command.CommandArg("{{ range .Match }}-m {{ . }} {{- end }}"),
|
||||||
command.CommandArg("{{ if .Source }}-s {{ .Source }}{{ end }}"),
|
command.CommandArg("{{ if .Source }}-s {{ .Source }}{{ end }}"),
|
||||||
@ -562,8 +736,9 @@ func NewIptableCreateCommand() *command.Command {
|
|||||||
command.CommandArg("{{ if .Destination }}-d {{ .Destination }}{{ end }}"),
|
command.CommandArg("{{ if .Destination }}-d {{ .Destination }}{{ end }}"),
|
||||||
command.CommandArg("{{ if .Dport }}--dport {{ .Dport }}{{ end }}"),
|
command.CommandArg("{{ if .Dport }}--dport {{ .Dport }}{{ end }}"),
|
||||||
command.CommandArg("{{ if .Proto }}-p {{ .Proto }}{{ end }}"),
|
command.CommandArg("{{ if .Proto }}-p {{ .Proto }}{{ end }}"),
|
||||||
command.CommandArg("{{ range .Flags }} --{{ .Name }} {{ .Value }} {{ end }}"),
|
command.CommandArg("{{ range .Flags -}} --{{ .Name }} {{ .Value }} {{- end }}"),
|
||||||
command.CommandArg("{{ if .Jump }}-j {{ .Jump }}{{ end }}"),
|
command.CommandArg("{{ if .Jump }}-j {{ .Jump }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ range .TargetFlags -}} --{{ .Name }} {{ .Value }}{{- end }}"),
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
@ -584,7 +759,7 @@ func IptableExtractRule(lineNumber uint, ruleLine string, target *Iptable) (stat
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if target.MatchRule(flags) {
|
if target.MatchRule(flags) {
|
||||||
target.Id = lineNumber
|
target.SetId(lineNumber)
|
||||||
state = "present"
|
state = "present"
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
@ -661,7 +836,7 @@ func NewIptableReadChainCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
for lineIndex, line := range lines[1:] {
|
for lineIndex, line := range lines[1:] {
|
||||||
i := (*IptableChainRules)[lineIndex]
|
i := (*IptableChainRules)[lineIndex]
|
||||||
i.Id = uint(lineIndex + 1)
|
i.SetId(uint(lineIndex + 1))
|
||||||
ruleFields := strings.Split(strings.TrimSpace(line), " ")
|
ruleFields := strings.Split(strings.TrimSpace(line), " ")
|
||||||
if ruleFields[0] == "-A" {
|
if ruleFields[0] == "-A" {
|
||||||
flags := ruleFields[2:]
|
flags := ruleFields[2:]
|
||||||
@ -682,7 +857,16 @@ func NewIptableUpdateCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewIptableDeleteCommand() *command.Command {
|
func NewIptableDeleteCommand() *command.Command {
|
||||||
return nil
|
c := command.NewCommand()
|
||||||
|
c.Path = "iptables"
|
||||||
|
c.Args = []command.CommandArg{
|
||||||
|
command.CommandArg("-t"),
|
||||||
|
command.CommandArg("{{ .Table }}"),
|
||||||
|
command.CommandArg("-D"),
|
||||||
|
command.CommandArg("{{ .Chain }}"),
|
||||||
|
command.CommandArg("{{ .Id }}"),
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIptableChainCreateCommand() *command.Command {
|
func NewIptableChainCreateCommand() *command.Command {
|
||||||
@ -766,7 +950,7 @@ func RuleExtractorMatchFlags(out []byte, target any) (err error) {
|
|||||||
slog.Info("RuleExtractorMatchFlags()", "flags", flags, "ipt", ipt)
|
slog.Info("RuleExtractorMatchFlags()", "flags", flags, "ipt", ipt)
|
||||||
err = nil
|
err = nil
|
||||||
ipt.Common.State = "present"
|
ipt.Common.State = "present"
|
||||||
ipt.Id = uint(linesIndex)
|
ipt.SetId(uint(linesIndex))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -838,6 +1022,14 @@ func NewIptableChainUpdateCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewIptableChainDeleteCommand() *command.Command {
|
func NewIptableChainDeleteCommand() *command.Command {
|
||||||
return nil
|
c := command.NewCommand()
|
||||||
|
c.Path = "iptables"
|
||||||
|
c.Args = []command.CommandArg{
|
||||||
|
command.CommandArg("-t"),
|
||||||
|
command.CommandArg("{{ .Table }}"),
|
||||||
|
command.CommandArg("-X"),
|
||||||
|
command.CommandArg("{{ .Chain }}"),
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ _ "syscall"
|
|||||||
"testing"
|
"testing"
|
||||||
_ "time"
|
_ "time"
|
||||||
"decl/internal/command"
|
"decl/internal/command"
|
||||||
|
"decl/internal/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewIptableResource(t *testing.T) {
|
func TestNewIptableResource(t *testing.T) {
|
||||||
@ -77,10 +78,59 @@ func TestReadIptable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateIptable(t *testing.T) {
|
func TestCreateIptable(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
testRule := NewIptable()
|
testRule := NewIptable()
|
||||||
assert.NotNil(t, testRule)
|
assert.NotNil(t, testRule)
|
||||||
|
|
||||||
|
declarationAttributes := `
|
||||||
|
table: "filter"
|
||||||
|
id: 5
|
||||||
|
chain: "INPUT"
|
||||||
|
source: "192.168.0.0/24"
|
||||||
|
destination: "192.168.0.1"
|
||||||
|
jump: "ACCEPT"
|
||||||
|
state: present
|
||||||
|
`
|
||||||
|
|
||||||
|
m := &MockCommand{
|
||||||
|
Executor: func(value any) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
Extractor: func(output []byte, target any) error {
|
||||||
|
testRule.Table = "filter"
|
||||||
|
testRule.Id = 3
|
||||||
|
testRule.Chain = "INPUT"
|
||||||
|
testRule.In = "eth0"
|
||||||
|
testRule.Source = "192.168.0.0/24"
|
||||||
|
testRule.State = "present"
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockReadChain := &MockCommand{
|
||||||
|
Executor: func(value any) ([]byte, error) {
|
||||||
|
return []byte(`
|
||||||
|
-P INPUT ACCEPT
|
||||||
|
-A INPUT -j LIBVIRT_INP
|
||||||
|
`), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
e := testRule.LoadDecl(declarationAttributes)
|
||||||
|
assert.Nil(t, e)
|
||||||
|
|
||||||
|
testRule.ReadChainCommand = (*command.Command)(mockReadChain)
|
||||||
|
testRule.ReadCommand = (*command.Command)(m)
|
||||||
|
testRule.CreateCommand = (*command.Command)(m)
|
||||||
|
|
||||||
|
assert.Nil(t, testRule.Create(ctx))
|
||||||
|
|
||||||
|
assert.Equal(t, uint(2), testRule.ChainLength)
|
||||||
|
_, err := testRule.Read(ctx)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
//assert.Equal(t, uint(3), testRule.ChainLength)
|
||||||
|
assert.Equal(t, uint(3), testRule.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIptableSetFlagValue(t *testing.T) {
|
func TestIptableSetFlagValue(t *testing.T) {
|
||||||
@ -117,6 +167,7 @@ func TestIptableRuleExtractorById(t *testing.T) {
|
|||||||
|
|
||||||
func TestIptableRuleExtractorByFlags(t *testing.T) {
|
func TestIptableRuleExtractorByFlags(t *testing.T) {
|
||||||
ipt := NewIptable()
|
ipt := NewIptable()
|
||||||
|
ipt.Resources = data.NewResourceMapper()
|
||||||
assert.NotNil(t, ipt)
|
assert.NotNil(t, ipt)
|
||||||
ipt.Table = IptableName("filter")
|
ipt.Table = IptableName("filter")
|
||||||
ipt.Chain = IptableChain("FOO")
|
ipt.Chain = IptableChain("FOO")
|
||||||
|
@ -18,6 +18,7 @@ _ "strconv"
|
|||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/folio"
|
"decl/internal/folio"
|
||||||
|
"decl/internal/command"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -25,52 +26,17 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"route"}, func(u *url.URL) data.Resource {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"route"}, func(u *url.URL) (n data.Resource) {
|
||||||
n := NewNetworkRoute()
|
n = NewNetworkRoute()
|
||||||
|
if u != nil {
|
||||||
|
if err := folio.CastParsedURI(u).ConstructResource(n); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
return n
|
return n
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
ROUTE := NODE_SPEC [ INFO_SPEC ]
|
|
||||||
NODE_SPEC := [ TYPE ] PREFIX [ tos TOS ]
|
|
||||||
[ table TABLE_ID ] [ proto RTPROTO ]
|
|
||||||
[ scope SCOPE ] [ metric METRIC ]
|
|
||||||
[ ttl-propagate { enabled | disabled } ]
|
|
||||||
INFO_SPEC := { NH | nhid ID } OPTIONS FLAGS [ nexthop NH ]...
|
|
||||||
|
|
||||||
NH := [ encap ENCAPTYPE ENCAPHDR ] [ via [ FAMILY ] ADDRESS ]
|
|
||||||
[ dev STRING ] [ weight NUMBER ] NHFLAGS
|
|
||||||
FAMILY := [ inet | inet6 | mpls | bridge | link ]
|
|
||||||
OPTIONS := FLAGS [ mtu NUMBER ] [ advmss NUMBER ] [ as [ to ] ADDRESS ]
|
|
||||||
[ rtt TIME ] [ rttvar TIME ] [ reordering NUMBER ]
|
|
||||||
[ window NUMBER ] [ cwnd NUMBER ] [ initcwnd NUMBER ]
|
|
||||||
[ ssthresh NUMBER ] [ realms REALM ] [ src ADDRESS ]
|
|
||||||
[ rto_min TIME ] [ hoplimit NUMBER ] [ initrwnd NUMBER ]
|
|
||||||
[ features FEATURES ] [ quickack BOOL ] [ congctl NAME ]
|
|
||||||
[ pref PREF ] [ expires TIME ] [ fastopen_no_cookie BOOL ]
|
|
||||||
NHFLAGS := [ onlink | pervasive ]
|
|
||||||
PREF := [ low | medium | high ]
|
|
||||||
TIME := NUMBER[s|ms]
|
|
||||||
BOOL := [1|0]
|
|
||||||
FEATURES := ecn
|
|
||||||
ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | rpl | ioam6 ]
|
|
||||||
ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL | IOAM6HDR ]
|
|
||||||
SEG6HDR := [ mode SEGMODE ] segs ADDR1,ADDRi,ADDRn [hmac HMACKEYID] [cleanup]
|
|
||||||
SEGMODE := [ encap | inline ]
|
|
||||||
SEG6LOCAL := action ACTION [ OPTIONS ] [ count ]
|
|
||||||
ACTION := { End | End.X | End.T | End.DX2 | End.DX6 | End.DX4 |
|
|
||||||
End.DT6 | End.DT4 | End.DT46 | End.B6 | End.B6.Encaps |
|
|
||||||
End.BM | End.S | End.AS | End.AM | End.BPF }
|
|
||||||
OPTIONS := OPTION [ OPTIONS ]
|
|
||||||
OPTION := { srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV |
|
|
||||||
table TABLEID | vrftable TABLEID | endpoint PROGNAME }
|
|
||||||
IOAM6HDR := trace prealloc type IOAM6_TRACE_TYPE ns IOAM6_NAMESPACE size IOAM6_TRACE_SIZE
|
|
||||||
ROUTE_GET_FLAGS := [ fibmatch ]
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
type NetworkRouteType string
|
type NetworkRouteType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -114,33 +80,45 @@ const (
|
|||||||
|
|
||||||
// Manage the state of network routes
|
// Manage the state of network routes
|
||||||
type NetworkRoute struct {
|
type NetworkRoute struct {
|
||||||
*Common `json:",inline" yaml:",inline"`
|
*Common `json:",inline" yaml:",inline"`
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
Id string
|
Id string
|
||||||
To string `json:"to" yaml:"to"`
|
To string `json:"to" yaml:"to"`
|
||||||
Interface string `json:"interface" yaml:"interface"`
|
Interface string `json:"interface" yaml:"interface"`
|
||||||
Gateway string `json:"gateway" yaml:"gateway"`
|
Gateway string `json:"gateway" yaml:"gateway"`
|
||||||
Metric uint `json:"metric" yaml:"metric"`
|
Metric uint `json:"metric" yaml:"metric"`
|
||||||
Rtid NetworkRouteTableId `json:"rtid" yaml:"rtid"`
|
Rtid NetworkRouteTableId `json:"rtid" yaml:"rtid"`
|
||||||
RouteType NetworkRouteType `json:"routetype" yaml:"routetype"`
|
RouteType NetworkRouteType `json:"routetype" yaml:"routetype"`
|
||||||
Scope NetworkRouteScope `json:"scope" yaml:"scope"`
|
Scope NetworkRouteScope `json:"scope" yaml:"scope"`
|
||||||
Proto NetworkRouteProto `json:"proto" yaml:"proto"`
|
Proto NetworkRouteProto `json:"proto" yaml:"proto"`
|
||||||
|
|
||||||
CreateCommand *Command `yaml:"-" json:"-"`
|
CreateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
ReadCommand *Command `yaml:"-" json:"-"`
|
ReadCommand *command.Command `yaml:"-" json:"-"`
|
||||||
UpdateCommand *Command `yaml:"-" json:"-"`
|
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
DeleteCommand *Command `yaml:"-" json:"-"`
|
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
||||||
|
|
||||||
config data.ConfigurationValueGetter
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNetworkRoute() *NetworkRoute {
|
func NewNetworkRoute() *NetworkRoute {
|
||||||
n := &NetworkRoute{Rtid: NetworkRouteTableMain, Common: &Common{ resourceType: NetworkRouteTypeName } }
|
n := &NetworkRoute{Rtid: NetworkRouteTableMain}
|
||||||
|
n.Common = NewCommon(NetworkRouteTypeName, false)
|
||||||
|
n.Common.NormalizePath = n.NormalizePath
|
||||||
n.CreateCommand, n.ReadCommand, n.UpdateCommand, n.DeleteCommand = n.NewCRUD()
|
n.CreateCommand, n.ReadCommand, n.UpdateCommand, n.DeleteCommand = n.NewCRUD()
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NetworkRoute) Init(u data.URIParser) (err error) {
|
||||||
|
if u == nil {
|
||||||
|
u = folio.URI(n.URI()).Parse()
|
||||||
|
}
|
||||||
|
return n.SetParsedURI(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetworkRoute) NormalizePath() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (n *NetworkRoute) SetResourceMapper(resources data.ResourceMapper) {
|
func (n *NetworkRoute) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
n.Resources = resources
|
n.Resources = resources
|
||||||
}
|
}
|
||||||
@ -210,14 +188,6 @@ func (n *NetworkRoute) URI() string {
|
|||||||
return fmt.Sprintf("route://%s", n.Id)
|
return fmt.Sprintf("route://%s", n.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NetworkRoute) SetURI(uri string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkRoute) UseConfig(config data.ConfigurationValueGetter) {
|
|
||||||
n.config = config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkRoute) Validate() error {
|
func (n *NetworkRoute) Validate() error {
|
||||||
return fmt.Errorf("failed")
|
return fmt.Errorf("failed")
|
||||||
}
|
}
|
||||||
@ -472,41 +442,41 @@ func (n *NetworkRoute) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (n *NetworkRoute) NewCRUD() (create *Command, read *Command, update *Command, del *Command) {
|
func (n *NetworkRoute) NewCRUD() (create *command.Command, read *command.Command, update *command.Command, del *command.Command) {
|
||||||
return NewNetworkRouteCreateCommand(), NewNetworkRouteReadCommand(), NewNetworkRouteUpdateCommand(), NewNetworkRouteDeleteCommand()
|
return NewNetworkRouteCreateCommand(), NewNetworkRouteReadCommand(), NewNetworkRouteUpdateCommand(), NewNetworkRouteDeleteCommand()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNetworkRouteCreateCommand() *Command {
|
func NewNetworkRouteCreateCommand() *command.Command {
|
||||||
c := NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "ip"
|
c.Path = "ip"
|
||||||
c.Args = []CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
CommandArg("route"),
|
command.CommandArg("route"),
|
||||||
CommandArg("add"),
|
command.CommandArg("add"),
|
||||||
CommandArg("{{ if .To }}to {{ .To }}{{ end }}"),
|
command.CommandArg("{{ if .To }}to {{ .To }}{{ end }}"),
|
||||||
CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"),
|
command.CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"),
|
||||||
CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"),
|
command.CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"),
|
||||||
CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"),
|
command.CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"),
|
||||||
CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"),
|
command.CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"),
|
||||||
CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"),
|
command.CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"),
|
||||||
CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"),
|
command.CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"),
|
||||||
CommandArg("{{ if .Metric }}metric {{ .Metric }}{{ end }}"),
|
command.CommandArg("{{ if .Metric }}metric {{ .Metric }}{{ end }}"),
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNetworkRouteReadCommand() *Command {
|
func NewNetworkRouteReadCommand() *command.Command {
|
||||||
c := NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "ip"
|
c.Path = "ip"
|
||||||
c.Args = []CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
CommandArg("route"),
|
command.CommandArg("route"),
|
||||||
CommandArg("show"),
|
command.CommandArg("show"),
|
||||||
CommandArg("{{ if .To }}to {{ .To }}{{ end }}"),
|
command.CommandArg("{{ if .To }}to {{ .To }}{{ end }}"),
|
||||||
CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"),
|
command.CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"),
|
||||||
CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"),
|
command.CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"),
|
||||||
CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"),
|
command.CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"),
|
||||||
CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"),
|
command.CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"),
|
||||||
CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"),
|
command.CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"),
|
||||||
CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"),
|
command.CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"),
|
||||||
}
|
}
|
||||||
c.Extractor = func(out []byte, target any) error {
|
c.Extractor = func(out []byte, target any) error {
|
||||||
n := target.(*NetworkRoute)
|
n := target.(*NetworkRoute)
|
||||||
@ -529,16 +499,16 @@ func NewNetworkRouteReadCommand() *Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNetworkRouteUpdateCommand() *Command {
|
func NewNetworkRouteUpdateCommand() *command.Command {
|
||||||
c := NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "ip"
|
c.Path = "ip"
|
||||||
c.Args = []CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
CommandArg("del"),
|
command.CommandArg("del"),
|
||||||
CommandArg("{{ .Name }}"),
|
command.CommandArg("{{ .Name }}"),
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNetworkRouteDeleteCommand() *Command {
|
func NewNetworkRouteDeleteCommand() *command.Command {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,9 @@ import (
|
|||||||
"decl/internal/tempdir"
|
"decl/internal/tempdir"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PackageTypeName TypeName = "package"
|
||||||
|
)
|
||||||
var (
|
var (
|
||||||
PackageTempDir tempdir.Path = "jx_package_resource"
|
PackageTempDir tempdir.Path = "jx_package_resource"
|
||||||
)
|
)
|
||||||
@ -50,7 +53,8 @@ var SupportedPackageTypes []PackageType = []PackageType{PackageTypeApk, PackageT
|
|||||||
var SystemPackageType PackageType = FindSystemPackageType()
|
var SystemPackageType PackageType = FindSystemPackageType()
|
||||||
|
|
||||||
type Package struct {
|
type Package struct {
|
||||||
stater machine.Stater `yaml:"-" json:"-"`
|
*Common `yaml:",inline" json:",inline"`
|
||||||
|
stater machine.Stater `yaml:"-" json:"-"`
|
||||||
Source string `yaml:"source,omitempty" json:"source,omitempty"`
|
Source string `yaml:"source,omitempty" json:"source,omitempty"`
|
||||||
Name string `yaml:"name" json:"name"`
|
Name string `yaml:"name" json:"name"`
|
||||||
Required string `json:"required,omitempty" yaml:"required,omitempty"`
|
Required string `json:"required,omitempty" yaml:"required,omitempty"`
|
||||||
@ -62,18 +66,12 @@ type Package struct {
|
|||||||
ReadCommand *command.Command `yaml:"-" json:"-"`
|
ReadCommand *command.Command `yaml:"-" json:"-"`
|
||||||
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
||||||
// state attributes
|
|
||||||
State string `yaml:"state,omitempty" json:"state,omitempty"`
|
|
||||||
config data.ConfigurationValueGetter
|
|
||||||
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"package", string(PackageTypeApk), string(PackageTypeApt), string(PackageTypeDeb), string(PackageTypeDnf), string(PackageTypeRpm), string(PackageTypePip), string(PackageTypeYum)}, func(u *url.URL) data.Resource {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"package", string(PackageTypeApk), string(PackageTypeApt), string(PackageTypeDeb), string(PackageTypeDnf), string(PackageTypeRpm), string(PackageTypePip), string(PackageTypeYum)}, func(u *url.URL) data.Resource {
|
||||||
p := NewPackage()
|
return ConstructNewPackage(u)
|
||||||
e := p.SetParsedURI(u)
|
|
||||||
slog.Info("PackageFactory SetParsedURI()", "error", e)
|
|
||||||
return p
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +86,41 @@ func FindSystemPackageType() PackageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewPackage() *Package {
|
func NewPackage() *Package {
|
||||||
return &Package{ PackageType: SystemPackageType }
|
return &Package{
|
||||||
|
Common: NewCommon(PackageTypeName, true),
|
||||||
|
PackageType: SystemPackageType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConstructNewPackage(uri *url.URL) (p *Package) {
|
||||||
|
p = NewPackage()
|
||||||
|
if uri != nil {
|
||||||
|
if err := folio.CastParsedURI(uri).ConstructResource(p); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Package) Init(u data.URIParser) error {
|
||||||
|
if u == nil {
|
||||||
|
u = folio.URI(p.URI()).Parse()
|
||||||
|
}
|
||||||
|
uri := u.URL()
|
||||||
|
|
||||||
|
p.Name = filepath.Join(uri.Hostname(), uri.Path)
|
||||||
|
p.Version = uri.Query().Get("version")
|
||||||
|
if p.Version == "" {
|
||||||
|
p.Version = "latest"
|
||||||
|
}
|
||||||
|
indicatedPackageType := PackageType(uri.Query().Get("type"))
|
||||||
|
if indicatedPackageType.Validate() != nil {
|
||||||
|
p.PackageType = SystemPackageType
|
||||||
|
}
|
||||||
|
|
||||||
|
p.CreateCommand, p.ReadCommand, p.UpdateCommand, p.DeleteCommand = p.PackageType.NewCRUD()
|
||||||
|
|
||||||
|
return p.SetParsedURI(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Package) SetResourceMapper(resources data.ResourceMapper) {
|
func (p *Package) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
@ -97,11 +129,11 @@ func (p *Package) SetResourceMapper(resources data.ResourceMapper) {
|
|||||||
|
|
||||||
func (p *Package) Clone() data.Resource {
|
func (p *Package) Clone() data.Resource {
|
||||||
newp := &Package {
|
newp := &Package {
|
||||||
|
Common: p.Common.Clone(),
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Required: p.Required,
|
Required: p.Required,
|
||||||
Version: p.Version,
|
Version: p.Version,
|
||||||
PackageType: p.PackageType,
|
PackageType: p.PackageType,
|
||||||
State: p.State,
|
|
||||||
}
|
}
|
||||||
newp.CreateCommand, newp.ReadCommand, newp.UpdateCommand, newp.DeleteCommand = newp.PackageType.NewCRUD()
|
newp.CreateCommand, newp.ReadCommand, newp.UpdateCommand, newp.DeleteCommand = newp.PackageType.NewCRUD()
|
||||||
return newp
|
return newp
|
||||||
@ -135,11 +167,11 @@ func (p *Package) Notify(m *machine.EventMessage) {
|
|||||||
if triggerErr := p.StateMachine().Trigger("state_read"); triggerErr == nil {
|
if triggerErr := p.StateMachine().Trigger("state_read"); triggerErr == nil {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
p.State = "absent"
|
p.Common.State = "absent"
|
||||||
panic(triggerErr)
|
panic(triggerErr)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.State = "absent"
|
p.Common.State = "absent"
|
||||||
if ! errors.Is(readErr, ErrResourceStateAbsent) {
|
if ! errors.Is(readErr, ErrResourceStateAbsent) {
|
||||||
panic(readErr)
|
panic(readErr)
|
||||||
}
|
}
|
||||||
@ -164,17 +196,17 @@ func (p *Package) Notify(m *machine.EventMessage) {
|
|||||||
if triggerErr := p.StateMachine().Trigger("deleted"); triggerErr == nil {
|
if triggerErr := p.StateMachine().Trigger("deleted"); triggerErr == nil {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
panic(triggerErr)
|
panic(triggerErr)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
panic(deleteErr)
|
panic(deleteErr)
|
||||||
}
|
}
|
||||||
case "absent":
|
case "absent":
|
||||||
p.State = "absent"
|
p.Common.State = "absent"
|
||||||
case "present", "created", "updated", "read":
|
case "present", "created", "updated", "read":
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
}
|
}
|
||||||
case machine.EXITSTATEEVENT:
|
case machine.EXITSTATEEVENT:
|
||||||
}
|
}
|
||||||
@ -192,36 +224,25 @@ func (p *Package) URI() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (p *Package) SetURI(uri string) error {
|
func (p *Package) SetParsedURI(uri data.URIParser) (err error) {
|
||||||
resourceUri, e := url.Parse(uri)
|
u := uri.URL()
|
||||||
if e == nil {
|
if u.Scheme == "package" {
|
||||||
e = p.SetParsedURI(resourceUri)
|
p.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
}
|
p.Version = u.Query().Get("version")
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Package) SetParsedURI(uri *url.URL) (err error) {
|
|
||||||
if uri.Scheme == "package" {
|
|
||||||
p.Name = filepath.Join(uri.Hostname(), uri.Path)
|
|
||||||
p.Version = uri.Query().Get("version")
|
|
||||||
if p.Version == "" {
|
if p.Version == "" {
|
||||||
p.Version = "latest"
|
p.Version = "latest"
|
||||||
}
|
}
|
||||||
indicatedPackageType := PackageType(uri.Query().Get("type"))
|
indicatedPackageType := PackageType(u.Query().Get("type"))
|
||||||
if indicatedPackageType.Validate() != nil {
|
if indicatedPackageType.Validate() != nil {
|
||||||
p.PackageType = SystemPackageType
|
p.PackageType = SystemPackageType
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("%w: %s is not a package resource ", ErrInvalidResourceURI, uri.String())
|
err = fmt.Errorf("%w: %s is not a package resource ", ErrInvalidResourceURI, u.String())
|
||||||
}
|
}
|
||||||
p.CreateCommand, p.ReadCommand, p.UpdateCommand, p.DeleteCommand = p.PackageType.NewCRUD()
|
p.CreateCommand, p.ReadCommand, p.UpdateCommand, p.DeleteCommand = p.PackageType.NewCRUD()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Package) UseConfig(config data.ConfigurationValueGetter) {
|
|
||||||
p.config = config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Package) JSON() ([]byte, error) {
|
func (p *Package) JSON() ([]byte, error) {
|
||||||
return json.Marshal(p)
|
return json.Marshal(p)
|
||||||
}
|
}
|
||||||
@ -331,6 +352,9 @@ func (p *Package) LoadDecl(yamlResourceDeclaration string) error {
|
|||||||
func (p *Package) Type() string { return "package" }
|
func (p *Package) Type() string { return "package" }
|
||||||
|
|
||||||
func (p *Package) Read(ctx context.Context) (resourceYaml []byte, err error) {
|
func (p *Package) Read(ctx context.Context) (resourceYaml []byte, err error) {
|
||||||
|
if p.Version == "latest" {
|
||||||
|
p.Version = ""
|
||||||
|
}
|
||||||
if p.ReadCommand.Exists() {
|
if p.ReadCommand.Exists() {
|
||||||
var out []byte
|
var out []byte
|
||||||
out, err = p.ReadCommand.Execute(p)
|
out, err = p.ReadCommand.Execute(p)
|
||||||
@ -495,10 +519,10 @@ func NewApkReadCommand() *command.Command {
|
|||||||
if packageName == p.Name {
|
if packageName == p.Name {
|
||||||
p.Name = packageName
|
p.Name = packageName
|
||||||
p.Version = packageVersion
|
p.Version = packageVersion
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
} else {
|
} else {
|
||||||
slog.Info("NewApkReadCommand().Extrctor() mismatch", "name", p.Name, "parsed", packageName)
|
slog.Info("NewApkReadCommand().Extrctor() mismatch", "name", p.Name, "parsed", packageName)
|
||||||
p.State = "absent"
|
p.Common.State = "absent"
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -554,7 +578,7 @@ func NewApkReadPackagesCommand() *command.Command {
|
|||||||
packageName := strings.Join(packageFields[0:numberOfFields - 2], "-")
|
packageName := strings.Join(packageFields[0:numberOfFields - 2], "-")
|
||||||
packageVersion := strings.Join(packageFields[numberOfFields - 2:numberOfFields - 1], "-")
|
packageVersion := strings.Join(packageFields[numberOfFields - 2:numberOfFields - 1], "-")
|
||||||
p.Name = packageName
|
p.Name = packageName
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
p.Version = packageVersion
|
p.Version = packageVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -595,16 +619,16 @@ func NewAptReadCommand() *command.Command {
|
|||||||
switch key {
|
switch key {
|
||||||
case "Package":
|
case "Package":
|
||||||
if value != p.Name {
|
if value != p.Name {
|
||||||
p.State = "absent"
|
p.Common.State = "absent"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case "Status":
|
case "Status":
|
||||||
statusFields := strings.SplitN(value, " ", 3)
|
statusFields := strings.SplitN(value, " ", 3)
|
||||||
if len(statusFields) > 1 {
|
if len(statusFields) > 1 {
|
||||||
if statusFields[2] == "installed" {
|
if statusFields[2] == "installed" {
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
} else {
|
} else {
|
||||||
p.State = "absent"
|
p.Common.State = "absent"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "Version":
|
case "Version":
|
||||||
@ -671,7 +695,7 @@ func NewAptReadPackagesCommand() *command.Command {
|
|||||||
packageName := packageFields[0]
|
packageName := packageFields[0]
|
||||||
packageVersion := installedPackage[1]
|
packageVersion := installedPackage[1]
|
||||||
p.Name = packageName
|
p.Name = packageName
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
p.Version = packageVersion
|
p.Version = packageVersion
|
||||||
p.PackageType = PackageTypeApt
|
p.PackageType = PackageTypeApt
|
||||||
}
|
}
|
||||||
@ -711,16 +735,16 @@ func NewDebReadCommand() *command.Command {
|
|||||||
switch key {
|
switch key {
|
||||||
case "Package":
|
case "Package":
|
||||||
if value != p.Name {
|
if value != p.Name {
|
||||||
p.State = "absent"
|
p.Common.State = "absent"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case "Status":
|
case "Status":
|
||||||
statusFields := strings.SplitN(value, " ", 3)
|
statusFields := strings.SplitN(value, " ", 3)
|
||||||
if len(statusFields) > 1 {
|
if len(statusFields) > 1 {
|
||||||
if statusFields[2] == "installed" {
|
if statusFields[2] == "installed" {
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
} else {
|
} else {
|
||||||
p.State = "absent"
|
p.Common.State = "absent"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "Version":
|
case "Version":
|
||||||
@ -787,7 +811,7 @@ func NewDebReadPackagesCommand() *command.Command {
|
|||||||
p.Version = packageVersionFields[0]
|
p.Version = packageVersionFields[0]
|
||||||
}
|
}
|
||||||
p.Name = packageName
|
p.Name = packageName
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
p.PackageType = PackageTypeDeb
|
p.PackageType = PackageTypeDeb
|
||||||
lineIndex++
|
lineIndex++
|
||||||
}
|
}
|
||||||
@ -833,7 +857,7 @@ func NewDnfReadCommand() *command.Command {
|
|||||||
packageName := strings.TrimSpace(strings.Join(packageNameField[0:lenName - 1], "."))
|
packageName := strings.TrimSpace(strings.Join(packageNameField[0:lenName - 1], "."))
|
||||||
|
|
||||||
if packageName == p.Name {
|
if packageName == p.Name {
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
packageVersionField := strings.Split(fields[1], ":")
|
packageVersionField := strings.Split(fields[1], ":")
|
||||||
if len(packageVersionField) > 1 {
|
if len(packageVersionField) > 1 {
|
||||||
//packageEpoch := strings.TrimSpace(packageVersionField[0])
|
//packageEpoch := strings.TrimSpace(packageVersionField[0])
|
||||||
@ -846,7 +870,7 @@ func NewDnfReadCommand() *command.Command {
|
|||||||
slog.Info("DnfReadCommaond.Extract()", "package", packageName, "package", p)
|
slog.Info("DnfReadCommaond.Extract()", "package", packageName, "package", p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.State = "absent"
|
p.Common.State = "absent"
|
||||||
slog.Info("Extract()", "package", p)
|
slog.Info("Extract()", "package", p)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -907,7 +931,7 @@ func NewDnfReadPackagesCommand() *command.Command {
|
|||||||
p.Version = packageVersionFields[0]
|
p.Version = packageVersionFields[0]
|
||||||
}
|
}
|
||||||
p.Name = packageName
|
p.Name = packageName
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
p.PackageType = PackageTypeDnf
|
p.PackageType = PackageTypeDnf
|
||||||
lineIndex++
|
lineIndex++
|
||||||
}
|
}
|
||||||
@ -949,13 +973,13 @@ func NewRpmReadCommand() *command.Command {
|
|||||||
packageVersion := strings.Join(packageFields[numberOfFields - 2:numberOfFields - 1], "-")
|
packageVersion := strings.Join(packageFields[numberOfFields - 2:numberOfFields - 1], "-")
|
||||||
slog.Info("Package[RPM].Extract()", "name", packageName, "version", packageVersion, "package", p)
|
slog.Info("Package[RPM].Extract()", "name", packageName, "version", packageVersion, "package", p)
|
||||||
if packageName == p.Name {
|
if packageName == p.Name {
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
p.Version = packageVersion
|
p.Version = packageVersion
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.State = "absent"
|
p.Common.State = "absent"
|
||||||
slog.Info("Extract()", "package", p)
|
slog.Info("Extract()", "package", p)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1013,13 +1037,13 @@ func NewPipReadCommand() *command.Command {
|
|||||||
packageName := packageFields[0]
|
packageName := packageFields[0]
|
||||||
packageVersion := packageFields[1]
|
packageVersion := packageFields[1]
|
||||||
if packageName == p.Name {
|
if packageName == p.Name {
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
p.Version = packageVersion
|
p.Version = packageVersion
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.State = "absent"
|
p.Common.State = "absent"
|
||||||
slog.Info("Extract()", "package", p)
|
slog.Info("Extract()", "package", p)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1083,7 +1107,7 @@ func NewYumReadCommand() *command.Command {
|
|||||||
//packageArch := strings.TrimSpace(packageNameField[1])
|
//packageArch := strings.TrimSpace(packageNameField[1])
|
||||||
|
|
||||||
if packageName == p.Name {
|
if packageName == p.Name {
|
||||||
p.State = "present"
|
p.Common.State = "present"
|
||||||
packageVersionField := strings.Split(fields[1], ":")
|
packageVersionField := strings.Split(fields[1], ":")
|
||||||
//packageEpoch := strings.TrimSpace(packageVersionField[0])
|
//packageEpoch := strings.TrimSpace(packageVersionField[0])
|
||||||
packageVersion := strings.TrimSpace(packageVersionField[1])
|
packageVersion := strings.TrimSpace(packageVersionField[1])
|
||||||
@ -1091,7 +1115,7 @@ func NewYumReadCommand() *command.Command {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.State = "absent"
|
p.Common.State = "absent"
|
||||||
slog.Info("Extract()", "package", p)
|
slog.Info("Extract()", "package", p)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,12 @@ package resource
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
_ "gopkg.in/yaml.v3"
|
|
||||||
_ "io"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
_ "net/http"
|
|
||||||
_ "net/http/httptest"
|
|
||||||
_ "net/url"
|
|
||||||
_ "os"
|
|
||||||
_ "strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"decl/internal/command"
|
"decl/internal/command"
|
||||||
|
"decl/internal/folio"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewPackageResource(t *testing.T) {
|
func TestNewPackageResource(t *testing.T) {
|
||||||
@ -354,6 +347,8 @@ type: %s
|
|||||||
assert.NotNil(t, p)
|
assert.NotNil(t, p)
|
||||||
loadErr := p.LoadDecl(decl)
|
loadErr := p.LoadDecl(decl)
|
||||||
assert.Nil(t, loadErr)
|
assert.Nil(t, loadErr)
|
||||||
|
assert.Nil(t, p.Init(nil))
|
||||||
|
|
||||||
p.ReadCommand = SystemPackageType.NewReadCommand()
|
p.ReadCommand = SystemPackageType.NewReadCommand()
|
||||||
/*
|
/*
|
||||||
p.ReadCommand.Executor = func(value any) ([]byte, error) {
|
p.ReadCommand.Executor = func(value any) ([]byte, error) {
|
||||||
@ -363,6 +358,7 @@ type: %s
|
|||||||
p.ResolveId(ctx)
|
p.ResolveId(ctx)
|
||||||
yaml, readErr := p.Read(ctx)
|
yaml, readErr := p.Read(ctx)
|
||||||
assert.ErrorIs(t, readErr, ErrResourceStateAbsent)
|
assert.ErrorIs(t, readErr, ErrResourceStateAbsent)
|
||||||
|
slog.Info("TestReadPackageError", "package", p, "common", p.Common, "yaml", yaml)
|
||||||
assert.YAMLEq(t, expected, string(yaml))
|
assert.YAMLEq(t, expected, string(yaml))
|
||||||
slog.Info("read()", "yaml", yaml)
|
slog.Info("read()", "yaml", yaml)
|
||||||
assert.Equal(t, "", p.Version)
|
assert.Equal(t, "", p.Version)
|
||||||
@ -375,7 +371,9 @@ func TestCreatePackage(t *testing.T) {
|
|||||||
func TestPackageSetURI(t *testing.T) {
|
func TestPackageSetURI(t *testing.T) {
|
||||||
p := NewPackage()
|
p := NewPackage()
|
||||||
assert.NotNil(t, p)
|
assert.NotNil(t, p)
|
||||||
e := p.SetURI("package://" + "12345_key?type=apk")
|
|
||||||
|
uri := folio.URI("package://" + "12345_key?type=apk").Parse()
|
||||||
|
e := p.Init(uri)
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
assert.Equal(t, "package", p.Type())
|
assert.Equal(t, "package", p.Type())
|
||||||
assert.Equal(t, "12345_key", p.Name)
|
assert.Equal(t, "12345_key", p.Name)
|
||||||
|
@ -44,11 +44,16 @@ var ErrPKIInvalidEncodingType error = errors.New("Invalid EncodingType")
|
|||||||
var ErrPKIFailedDecodingPemBlock error = errors.New("Failed decoding pem block")
|
var ErrPKIFailedDecodingPemBlock error = errors.New("Failed decoding pem block")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ResourceTypes.Register([]string{"pki"}, func(u *url.URL) data.Resource {
|
ResourceTypes.Register([]string{"pki"}, func(u *url.URL) (data.Resource) {
|
||||||
k := NewPKI()
|
k := NewPKI()
|
||||||
ref := folio.ResourceReference(filepath.Join(u.Hostname(), u.Path))
|
if u != nil {
|
||||||
if len(ref) > 0 {
|
if err := folio.CastParsedURI(u).ConstructResource(k); err != nil {
|
||||||
k.PrivateKeyRef = ref
|
panic(err)
|
||||||
|
}
|
||||||
|
ref := folio.ResourceReference(filepath.Join(u.Hostname(), u.Path))
|
||||||
|
if len(ref) > 0 {
|
||||||
|
k.PrivateKeyRef = ref
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return k
|
return k
|
||||||
})
|
})
|
||||||
@ -83,8 +88,6 @@ type PKI struct {
|
|||||||
|
|
||||||
Bits int `json:"bits" yaml:"bits"`
|
Bits int `json:"bits" yaml:"bits"`
|
||||||
EncodingType EncodingType `json:"type" yaml:"type"`
|
EncodingType EncodingType `json:"type" yaml:"type"`
|
||||||
//State string `json:"state,omitempty" yaml:"state,omitempty"`
|
|
||||||
config data.ConfigurationValueGetter
|
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +105,6 @@ func (k *PKI) Clone() data.Resource {
|
|||||||
return &PKI {
|
return &PKI {
|
||||||
Common: k.Common.Clone(),
|
Common: k.Common.Clone(),
|
||||||
EncodingType: k.EncodingType,
|
EncodingType: k.EncodingType,
|
||||||
//State: k.State,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,6 +121,16 @@ func (k *PKI) Notify(m *machine.EventMessage) {
|
|||||||
switch m.On {
|
switch m.On {
|
||||||
case machine.ENTERSTATEEVENT:
|
case machine.ENTERSTATEEVENT:
|
||||||
switch m.Dest {
|
switch m.Dest {
|
||||||
|
case "start_stat":
|
||||||
|
if statErr := k.ReadStat(); statErr == nil {
|
||||||
|
if triggerErr := k.StateMachine().Trigger("exists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if triggerErr := k.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
case "start_read":
|
case "start_read":
|
||||||
if _,readErr := k.Read(ctx); readErr == nil {
|
if _,readErr := k.Read(ctx); readErr == nil {
|
||||||
if triggerErr := k.StateMachine().Trigger("state_read"); triggerErr == nil {
|
if triggerErr := k.StateMachine().Trigger("state_read"); triggerErr == nil {
|
||||||
@ -175,22 +187,6 @@ func (k *PKI) URI() string {
|
|||||||
return fmt.Sprintf("pki://%s", filepath.Join(u.Hostname(), u.RequestURI()))
|
return fmt.Sprintf("pki://%s", filepath.Join(u.Hostname(), u.RequestURI()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *PKI) SetURI(uri string) error {
|
|
||||||
resourceUri, e := url.Parse(uri)
|
|
||||||
if e == nil {
|
|
||||||
if resourceUri.Scheme == "pki" {
|
|
||||||
k.PrivateKeyRef = folio.ResourceReference(fmt.Sprintf("pki://%s", filepath.Join(resourceUri.Hostname(), resourceUri.Path)))
|
|
||||||
} else {
|
|
||||||
e = fmt.Errorf("%w: %s is not a cert", ErrInvalidResourceURI, uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *PKI) UseConfig(config data.ConfigurationValueGetter) {
|
|
||||||
k.config = config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *PKI) Validate() error {
|
func (k *PKI) Validate() error {
|
||||||
return fmt.Errorf("failed")
|
return fmt.Errorf("failed")
|
||||||
}
|
}
|
||||||
@ -228,6 +224,27 @@ func (k *PKI) LoadDecl(yamlResourceDeclaration string) error {
|
|||||||
return k.LoadString(yamlResourceDeclaration, codec.FormatYaml)
|
return k.LoadString(yamlResourceDeclaration, codec.FormatYaml)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *PKI) ReadStat() (err error) {
|
||||||
|
var resourcesErr []string
|
||||||
|
if ! k.PrivateKeyRef.Exists() {
|
||||||
|
resourcesErr = append(resourcesErr, string(k.PrivateKeyRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! k.PublicKeyRef.Exists() {
|
||||||
|
resourcesErr = append(resourcesErr, string(k.PublicKeyRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! k.CertificateRef.Exists() {
|
||||||
|
resourcesErr = append(resourcesErr, string(k.CertificateRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resourcesErr) > 0 {
|
||||||
|
err = fmt.Errorf("PKI resources missing: %s", strings.Join(resourcesErr, ","))
|
||||||
|
// k.State = "absent"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (k *PKI) ResolveId(ctx context.Context) string {
|
func (k *PKI) ResolveId(ctx context.Context) string {
|
||||||
return string(k.PrivateKeyRef)
|
return string(k.PrivateKeyRef)
|
||||||
}
|
}
|
||||||
@ -463,62 +480,6 @@ func (k *PKI) Update(ctx context.Context) (err error) {
|
|||||||
|
|
||||||
func (k *PKI) Type() string { return "pki" }
|
func (k *PKI) Type() string { return "pki" }
|
||||||
|
|
||||||
/*
|
|
||||||
func (k *PKI) UnmarshalValue(value *PKIContent) error {
|
|
||||||
d.Type = value.Type
|
|
||||||
d.Transition = value.Transition
|
|
||||||
d.Config = value.Config
|
|
||||||
newResource, resourceErr := ResourceTypes.New(fmt.Sprintf("%s://", value.Type))
|
|
||||||
if resourceErr != nil {
|
|
||||||
slog.Info("Declaration.UnmarshalValue", "value", value, "error", resourceErr)
|
|
||||||
return resourceErr
|
|
||||||
}
|
|
||||||
d.Attributes = newResource
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *PKI) UnmarshalYAML(value *yaml.Node) error {
|
|
||||||
t := &DeclarationType{}
|
|
||||||
if unmarshalResourceTypeErr := value.Decode(t); unmarshalResourceTypeErr != nil {
|
|
||||||
return unmarshalResourceTypeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := d.UnmarshalValue(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceAttrs := struct {
|
|
||||||
Attributes yaml.Node `json:"attributes"`
|
|
||||||
}{}
|
|
||||||
if unmarshalAttributesErr := value.Decode(&resourceAttrs); unmarshalAttributesErr != nil {
|
|
||||||
return unmarshalAttributesErr
|
|
||||||
}
|
|
||||||
if unmarshalResourceErr := resourceAttrs.Attributes.Decode(d.Attributes); unmarshalResourceErr != nil {
|
|
||||||
return unmarshalResourceErr
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Declaration) UnmarshalJSON(data []byte) error {
|
|
||||||
t := &DeclarationType{}
|
|
||||||
if unmarshalResourceTypeErr := json.Unmarshal(data, t); unmarshalResourceTypeErr != nil {
|
|
||||||
return unmarshalResourceTypeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := d.UnmarshalValue(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceAttrs := struct {
|
|
||||||
Attributes Resource `json:"attributes"`
|
|
||||||
}{Attributes: d.Attributes}
|
|
||||||
if unmarshalAttributesErr := json.Unmarshal(data, &resourceAttrs); unmarshalAttributesErr != nil {
|
|
||||||
return unmarshalAttributesErr
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (t *EncodingType) UnmarshalValue(value string) error {
|
func (t *EncodingType) UnmarshalValue(value string) error {
|
||||||
switch value {
|
switch value {
|
||||||
case string(EncodingTypePem):
|
case string(EncodingTypePem):
|
||||||
|
@ -4,12 +4,12 @@ package resource
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "encoding/json"
|
_ "encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
_ "gopkg.in/yaml.v3"
|
_ "gopkg.in/yaml.v3"
|
||||||
"io"
|
"io"
|
||||||
_ "log"
|
_ "log"
|
||||||
_ "os"
|
_ "os"
|
||||||
"decl/internal/transport"
|
"decl/internal/transport"
|
||||||
"decl/internal/ext"
|
"decl/internal/ext"
|
||||||
@ -19,7 +19,6 @@ _ "os"
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"net/url"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestResourceMapper func(key string) (data.Declaration, bool)
|
type TestResourceMapper func(key string) (data.Declaration, bool)
|
||||||
@ -36,6 +35,9 @@ func (rm TestResourceMapper) Has(key string) (ok bool) {
|
|||||||
func (rm TestResourceMapper) Set(key string, value data.Declaration) {
|
func (rm TestResourceMapper) Set(key string, value data.Declaration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rm TestResourceMapper) Delete(key string) {
|
||||||
|
}
|
||||||
|
|
||||||
type StringContentReadWriter func() (any, error)
|
type StringContentReadWriter func() (any, error)
|
||||||
|
|
||||||
func (s StringContentReadWriter) ContentWriterStream() (*transport.Writer, error) {
|
func (s StringContentReadWriter) ContentWriterStream() (*transport.Writer, error) {
|
||||||
@ -56,7 +58,7 @@ func (s StringContentReadWriter) LoadString(docData string, f codec.Format) (err
|
|||||||
func (s StringContentReadWriter) LoadDecl(yamlResourceDeclaration string) error { return nil }
|
func (s StringContentReadWriter) LoadDecl(yamlResourceDeclaration string) error { return nil }
|
||||||
func (s StringContentReadWriter) ResolveId(ctx context.Context) (string) { return "" }
|
func (s StringContentReadWriter) ResolveId(ctx context.Context) (string) { return "" }
|
||||||
func (s StringContentReadWriter) SetURI(uri string) (error) { return nil }
|
func (s StringContentReadWriter) SetURI(uri string) (error) { return nil }
|
||||||
func (s StringContentReadWriter) SetParsedURI(uri *url.URL) (error) { return nil }
|
func (s StringContentReadWriter) SetParsedURI(uri data.URIParser) (error) { return nil }
|
||||||
func (s StringContentReadWriter) URI() (string) { return "" }
|
func (s StringContentReadWriter) URI() (string) { return "" }
|
||||||
func (s StringContentReadWriter) Validate() (error) { return nil }
|
func (s StringContentReadWriter) Validate() (error) { return nil }
|
||||||
func (s StringContentReadWriter) ResourceType() data.TypeName { return "" }
|
func (s StringContentReadWriter) ResourceType() data.TypeName { return "" }
|
||||||
|
@ -20,67 +20,6 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ResourceReference string
|
type ResourceReference string
|
||||||
/*
|
|
||||||
type ResourceSelector func(r *Declaration) bool
|
|
||||||
|
|
||||||
type Resource interface {
|
|
||||||
Type() string
|
|
||||||
StateMachine() machine.Stater
|
|
||||||
URI() string
|
|
||||||
SetURI(string) error
|
|
||||||
UseConfig(config ConfigurationValueGetter)
|
|
||||||
ResolveId(context.Context) string
|
|
||||||
ResourceLoader
|
|
||||||
StateTransformer
|
|
||||||
ResourceReader
|
|
||||||
ResourceValidator
|
|
||||||
Clone() Resource
|
|
||||||
SetResourceMapper(resources ResourceMapper)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContentReader interface {
|
|
||||||
ContentReaderStream() (*transport.Reader, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContentWriter interface {
|
|
||||||
ContentWriterStream() (*transport.Writer, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContentReadWriter interface {
|
|
||||||
ContentReader
|
|
||||||
ContentWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourceValidator interface {
|
|
||||||
Validate() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourceCreator interface {
|
|
||||||
Create(context.Context) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourceReader interface {
|
|
||||||
Read(context.Context) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourceUpdater interface {
|
|
||||||
Update(context.Context) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourceDeleter interface {
|
|
||||||
Delete(context.Context) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourceDecoder struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourceCrudder struct {
|
|
||||||
ResourceCreator
|
|
||||||
ResourceReader
|
|
||||||
ResourceUpdater
|
|
||||||
ResourceDeleter
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func NewResource(uri string) data.Resource {
|
func NewResource(uri string) data.Resource {
|
||||||
r, e := ResourceTypes.New(uri)
|
r, e := ResourceTypes.New(uri)
|
||||||
@ -90,50 +29,17 @@ func NewResource(uri string) data.Resource {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
func ResourceConstructor(res data.Resource, uri data.URIParser) (err error) {
|
||||||
|
if uri != nil {
|
||||||
// Return a Content ReadWriter for the resource referred to.
|
return uri.ConstructResource(res)
|
||||||
func (r ResourceReference) Lookup(look data.ResourceMapper) data.ContentReadWriter {
|
} else {
|
||||||
slog.Info("ResourceReference.Lookup()", "resourcereference", r, "resourcemapper", look)
|
if ri, ok := res.(data.ResourceInitializer); ok {
|
||||||
if look != nil {
|
return ri.Init(uri)
|
||||||
if v,ok := look.Get(string(r)); ok {
|
|
||||||
return v.(data.ContentReadWriter)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return r
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r ResourceReference) Dereference(look data.ResourceMapper) data.Resource {
|
|
||||||
slog.Info("ResourceReference.Dereference()", "resourcereference", r, "resourcemapper", look)
|
|
||||||
if look != nil {
|
|
||||||
if v,ok := look.Get(string(r)); ok {
|
|
||||||
return v.(*Declaration).Attributes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r ResourceReference) Parse() *url.URL {
|
|
||||||
u, e := url.Parse(string(r))
|
|
||||||
if e == nil {
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r ResourceReference) Exists() bool {
|
|
||||||
return transport.ExistsURI(string(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r ResourceReference) ContentReaderStream() (*transport.Reader, error) {
|
|
||||||
return transport.NewReaderURI(string(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r ResourceReference) ContentWriterStream() (*transport.Writer, error) {
|
|
||||||
return transport.NewWriterURI(string(r))
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func StorageMachine(sub machine.Subscriber) machine.Stater {
|
func StorageMachine(sub machine.Subscriber) machine.Stater {
|
||||||
// start_destroy -> absent -> start_create -> present -> start_destroy
|
// start_destroy -> absent -> start_create -> present -> start_destroy
|
||||||
stater := machine.New("unknown")
|
stater := machine.New("unknown")
|
||||||
|
@ -58,6 +58,8 @@ func TestSchemaValidateJSON(t *testing.T) {
|
|||||||
testFile := NewFile()
|
testFile := NewFile()
|
||||||
e := testFile.LoadDecl(decl)
|
e := testFile.LoadDecl(decl)
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
|
assert.Equal(t, file, testFile.ResolveId(ctx))
|
||||||
|
|
||||||
fileApplyErr := testFile.Apply()
|
fileApplyErr := testFile.Apply()
|
||||||
assert.Nil(t, fileApplyErr)
|
assert.Nil(t, fileApplyErr)
|
||||||
|
|
||||||
@ -71,8 +73,10 @@ func TestSchemaValidateJSON(t *testing.T) {
|
|||||||
assert.NotNil(t, f)
|
assert.NotNil(t, f)
|
||||||
|
|
||||||
f.Path = file
|
f.Path = file
|
||||||
|
assert.Nil(t, f.Init(nil))
|
||||||
r, e := f.Read(ctx)
|
r, e := f.Read(ctx)
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
|
|
||||||
assert.Equal(t, ProcessTestUserName, f.Owner)
|
assert.Equal(t, ProcessTestUserName, f.Owner)
|
||||||
|
|
||||||
info, statErr := os.Stat(file)
|
info, statErr := os.Stat(file)
|
||||||
@ -86,8 +90,8 @@ func TestSchemaValidateJSON(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSchemaValidateSchema(t *testing.T) {
|
func TestSchemaValidateSchema(t *testing.T) {
|
||||||
s := NewSchema("document")
|
s := NewSchema("document")
|
||||||
assert.NotNil(t, s)
|
assert.NotNil(t, s)
|
||||||
|
|
||||||
assert.Nil(t, s.ValidateSchema())
|
assert.Nil(t, s.ValidateSchema())
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ package resource
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "log/slog"
|
"log/slog"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"io"
|
"io"
|
||||||
@ -14,8 +14,15 @@ _ "log/slog"
|
|||||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
|
"decl/internal/command"
|
||||||
|
"decl/internal/folio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnsupportedServiceManagerType error = errors.New("Unsupported service manager")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -29,32 +36,72 @@ const (
|
|||||||
ServiceManagerTypeSysV ServiceManagerType = "sysv"
|
ServiceManagerTypeSysV ServiceManagerType = "sysv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SysVStatusRunning int = 0
|
||||||
|
SysVStatusStopped int = 1
|
||||||
|
SysVStatusUnknown int = 2
|
||||||
|
SysVStatusMissing int = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SupportedServiceManagerTypes []ServiceManagerType = []ServiceManagerType{ServiceManagerTypeSystemd, ServiceManagerTypeSysV}
|
||||||
|
SystemServiceManagerType ServiceManagerType = FindSystemServiceManagerType()
|
||||||
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
*Common `yaml:",inline" json:",inline"`
|
*Common `yaml:",inline" json:",inline"`
|
||||||
stater machine.Stater `yaml:"-" json:"-"`
|
stater machine.Stater `yaml:"-" json:"-"`
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
ServiceManagerType ServiceManagerType `json:"servicemanager,omitempty" yaml:"servicemanager,omitempty"`
|
ServiceManagerType ServiceManagerType `json:"servicemanager,omitempty" yaml:"servicemanager,omitempty"`
|
||||||
|
|
||||||
CreateCommand *Command `yaml:"-" json:"-"`
|
CreateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
ReadCommand *Command `yaml:"-" json:"-"`
|
ReadCommand *command.Command `yaml:"-" json:"-"`
|
||||||
UpdateCommand *Command `yaml:"-" json:"-"`
|
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
DeleteCommand *Command `yaml:"-" json:"-"`
|
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
||||||
|
|
||||||
config data.ConfigurationValueGetter
|
|
||||||
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ResourceTypes.Register([]string{"service"}, func(u *url.URL) data.Resource {
|
ResourceTypes.Register([]string{"service"}, func(u *url.URL) data.Resource {
|
||||||
s := NewService()
|
s := NewService()
|
||||||
s.Name = filepath.Join(u.Hostname(), u.Path)
|
if err := folio.CastParsedURI(u).ConstructResource(s); err != nil {
|
||||||
s.CreateCommand, s.ReadCommand, s.UpdateCommand, s.DeleteCommand = s.ServiceManagerType.NewCRUD()
|
panic(err)
|
||||||
|
}
|
||||||
return s
|
return s
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService() *Service {
|
func FindSystemServiceManagerType() ServiceManagerType {
|
||||||
return &Service{ ServiceManagerType: ServiceManagerTypeSystemd, Common: &Common{ resourceType: ServiceTypeName } }
|
for _, servicemanagerType := range SupportedServiceManagerTypes {
|
||||||
|
if c := servicemanagerType.NewReadCommand(); c != nil && c.Exists() {
|
||||||
|
return servicemanagerType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ServiceManagerTypeSystemd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService() (s *Service) {
|
||||||
|
s = &Service{ ServiceManagerType: SystemServiceManagerType }
|
||||||
|
s.Common = NewCommon(ServiceTypeName, false)
|
||||||
|
s.Common.NormalizePath = s.NormalizePath
|
||||||
|
s.CreateCommand, s.ReadCommand, s.UpdateCommand, s.DeleteCommand = s.ServiceManagerType.NewCRUD()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Init(u data.URIParser) (err error) {
|
||||||
|
if u == nil {
|
||||||
|
u = folio.URI(s.URI()).Parse()
|
||||||
|
}
|
||||||
|
uri := u.URL()
|
||||||
|
err = s.SetParsedURI(u)
|
||||||
|
s.Name = filepath.Join(uri.Hostname(), uri.Path)
|
||||||
|
s.CreateCommand, s.ReadCommand, s.UpdateCommand, s.DeleteCommand = s.ServiceManagerType.NewCRUD()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) NormalizePath() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) StateMachine() machine.Stater {
|
func (s *Service) StateMachine() machine.Stater {
|
||||||
@ -69,6 +116,16 @@ func (s *Service) Notify(m *machine.EventMessage) {
|
|||||||
switch m.On {
|
switch m.On {
|
||||||
case machine.ENTERSTATEEVENT:
|
case machine.ENTERSTATEEVENT:
|
||||||
switch m.Dest {
|
switch m.Dest {
|
||||||
|
case "start_stat":
|
||||||
|
if statErr := s.ReadStat(); statErr == nil {
|
||||||
|
if triggerErr := s.StateMachine().Trigger("exists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if triggerErr := s.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
case "start_create":
|
case "start_create":
|
||||||
if e := s.Create(ctx); e == nil {
|
if e := s.Create(ctx); e == nil {
|
||||||
if triggerErr := s.stater.Trigger("created"); triggerErr == nil {
|
if triggerErr := s.stater.Trigger("created"); triggerErr == nil {
|
||||||
@ -76,10 +133,24 @@ func (s *Service) Notify(m *machine.EventMessage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.Common.State = "absent"
|
s.Common.State = "absent"
|
||||||
case "created":
|
case "start_read":
|
||||||
|
if _,readErr := s.Read(ctx); readErr == nil {
|
||||||
|
if triggerErr := s.stater.Trigger("state_read"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
s.Common.State = "absent"
|
||||||
|
panic(triggerErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.Common.State = "absent"
|
||||||
|
panic(readErr)
|
||||||
|
}
|
||||||
|
case "present", "created", "read":
|
||||||
s.Common.State = "present"
|
s.Common.State = "present"
|
||||||
case "running":
|
case "running":
|
||||||
s.Common.State = "running"
|
s.Common.State = "running"
|
||||||
|
case "absent":
|
||||||
|
s.Common.State = "absent"
|
||||||
}
|
}
|
||||||
case machine.EXITSTATEEVENT:
|
case machine.EXITSTATEEVENT:
|
||||||
}
|
}
|
||||||
@ -89,20 +160,20 @@ func (s *Service) URI() string {
|
|||||||
return fmt.Sprintf("service://%s", s.Name)
|
return fmt.Sprintf("service://%s", s.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) SetURI(uri string) error {
|
func (s *Service) SetParsedURI(uri data.URIParser) (err error) {
|
||||||
resourceUri, e := url.Parse(uri)
|
if err = s.Common.SetParsedURI(uri); err == nil {
|
||||||
if e == nil {
|
err = s.setFieldsFromPath()
|
||||||
if resourceUri.Scheme == s.Type() {
|
|
||||||
s.Name = filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI())
|
|
||||||
} else {
|
|
||||||
e = fmt.Errorf("%w: %s is not a %s", ErrInvalidResourceURI, uri, s.Type())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return e
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) UseConfig(config data.ConfigurationValueGetter) {
|
func (s *Service) setFieldsFromPath() (err error) {
|
||||||
s.config = config
|
if len(s.Common.Path) > 0 {
|
||||||
|
s.Name = s.Common.Path
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%w: %s is not an iptable rule", ErrInvalidResourceURI, s.Common.URI())
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) JSON() ([]byte, error) {
|
func (s *Service) JSON() ([]byte, error) {
|
||||||
@ -167,7 +238,7 @@ func (s *Service) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServiceManagerType) NewCRUD() (create *Command, read *Command, update *Command, del *Command) {
|
func (s *ServiceManagerType) NewCRUD() (create *command.Command, read *command.Command, update *command.Command, del *command.Command) {
|
||||||
switch *s {
|
switch *s {
|
||||||
case ServiceManagerTypeSystemd:
|
case ServiceManagerTypeSystemd:
|
||||||
return NewSystemdCreateCommand(), NewSystemdReadCommand(), NewSystemdUpdateCommand(), NewSystemdDeleteCommand()
|
return NewSystemdCreateCommand(), NewSystemdReadCommand(), NewSystemdUpdateCommand(), NewSystemdDeleteCommand()
|
||||||
@ -178,14 +249,52 @@ func (s *ServiceManagerType) NewCRUD() (create *Command, read *Command, update *
|
|||||||
return nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServiceManagerType) NewReadCommand() (read *command.Command) {
|
||||||
func (s *Service) Create(ctx context.Context) error {
|
switch *s {
|
||||||
|
case ServiceManagerTypeSystemd:
|
||||||
|
return NewSystemdReadCommand()
|
||||||
|
case ServiceManagerTypeSysV:
|
||||||
|
return NewSysVReadCommand()
|
||||||
|
default:
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Read(ctx context.Context) ([]byte, error) {
|
func (s *Service) Create(ctx context.Context) (err error) {
|
||||||
|
var out []byte
|
||||||
return yaml.Marshal(s)
|
out, err = s.CreateCommand.Execute(s)
|
||||||
|
slog.Info("Service.Create()", "out", out, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ReadStat() (err error) {
|
||||||
|
if s.ReadCommand.Exists() {
|
||||||
|
_, err = s.ReadCommand.Execute(s)
|
||||||
|
} else {
|
||||||
|
err = ErrUnsupportedServiceManagerType
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Read(ctx context.Context) (resourceYaml []byte, err error) {
|
||||||
|
if s.ReadCommand.Exists() {
|
||||||
|
var out []byte
|
||||||
|
out, err = s.ReadCommand.Execute(s)
|
||||||
|
if err == nil {
|
||||||
|
err = s.ReadCommand.Extractor(out, s)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%w - %w", ErrResourceStateAbsent, err)
|
||||||
|
}
|
||||||
|
slog.Info("Service.Read()", "service", s, "error", err)
|
||||||
|
} else {
|
||||||
|
err = ErrUnsupportedServiceManagerType
|
||||||
|
}
|
||||||
|
var yamlErr error
|
||||||
|
resourceYaml, yamlErr = yaml.Marshal(s)
|
||||||
|
if err == nil {
|
||||||
|
err = yamlErr
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Update(ctx context.Context) error {
|
func (s *Service) Update(ctx context.Context) error {
|
||||||
@ -202,22 +311,22 @@ func (s *Service) ResolveId(ctx context.Context) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemdCreateCommand() *Command {
|
func NewSystemdCreateCommand() *command.Command {
|
||||||
c := NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "systemctl"
|
c.Path = "systemctl"
|
||||||
c.Args = []CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
CommandArg("enable"),
|
command.CommandArg("enable"),
|
||||||
CommandArg("{{ .Name }}"),
|
command.CommandArg("{{ .Name }}"),
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemdReadCommand() *Command {
|
func NewSystemdReadCommand() *command.Command {
|
||||||
c := NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "systemctl"
|
c.Path = "systemctl"
|
||||||
c.Args = []CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
CommandArg("show"),
|
command.CommandArg("show"),
|
||||||
CommandArg("{{ .Name }}"),
|
command.CommandArg("{{ .Name }}"),
|
||||||
}
|
}
|
||||||
c.Extractor = func(out []byte, target any) error {
|
c.Extractor = func(out []byte, target any) error {
|
||||||
s := target.(*Service)
|
s := target.(*Service)
|
||||||
@ -253,26 +362,63 @@ func NewSystemdReadCommand() *Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemdUpdateCommand() *Command {
|
func NewSystemdUpdateCommand() *command.Command {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemdDeleteCommand() *Command {
|
func NewSystemdDeleteCommand() *command.Command {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSysVCreateCommand() *Command {
|
func NewSysVCreateCommand() *command.Command {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSysVReadCommand() *Command {
|
func NewSysVReadCommand() *command.Command {
|
||||||
|
c := command.NewCommand()
|
||||||
|
c.Path = "service"
|
||||||
|
c.Args = []command.CommandArg{
|
||||||
|
command.CommandArg("status"),
|
||||||
|
command.CommandArg("{{ .Name }}"),
|
||||||
|
}
|
||||||
|
c.Extractor = func(out []byte, target any) (err error) {
|
||||||
|
s := target.(*Service)
|
||||||
|
/*
|
||||||
|
serviceStatus := strings.Split(string(out), "\n")
|
||||||
|
for _, statusLine := range(serviceStatus) {
|
||||||
|
if len(statusLine) > 1 {
|
||||||
|
statusFields := strings.Fields(statusLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if len(out) < 1 {
|
||||||
|
if err = s.stater.Trigger("notexist"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch c.ExitCode {
|
||||||
|
case SysVStatusRunning:
|
||||||
|
if err = s.stater.Trigger("running"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case SysVStatusStopped:
|
||||||
|
if err = s.stater.Trigger("created"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case SysVStatusUnknown, SysVStatusMissing:
|
||||||
|
if err = s.stater.Trigger("notexist"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSysVUpdateCommand() *Command {
|
func NewSysVUpdateCommand() *command.Command {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSysVDeleteCommand() *Command {
|
func NewSysVDeleteCommand() *command.Command {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,10 @@ package resource
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "decl/tests/mocks"
|
"decl/internal/command"
|
||||||
_ "fmt"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
|
"decl/internal/folio"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewServiceResource(t *testing.T) {
|
func TestNewServiceResource(t *testing.T) {
|
||||||
@ -17,20 +17,35 @@ func TestNewServiceResource(t *testing.T) {
|
|||||||
|
|
||||||
func TestUriServiceResource(t *testing.T) {
|
func TestUriServiceResource(t *testing.T) {
|
||||||
c := NewService()
|
c := NewService()
|
||||||
assert.Nil(t, c.SetURI("service://ssh"))
|
uri := folio.URI("service://ssh").Parse()
|
||||||
|
|
||||||
|
assert.Nil(t, c.Init(uri))
|
||||||
assert.Equal(t, "ssh", c.Name)
|
assert.Equal(t, "ssh", c.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadServiceResource(t *testing.T) {
|
func TestReadServiceResource(t *testing.T) {
|
||||||
|
|
||||||
|
s := NewService()
|
||||||
|
s.Name = "ssh"
|
||||||
|
|
||||||
yamlResult := `
|
yamlResult := `
|
||||||
name: "ssh"
|
name: "ssh"
|
||||||
servicemanager: "systemd"
|
servicemanager: "systemd"
|
||||||
state: "present"
|
state: "present"
|
||||||
`
|
`
|
||||||
c := NewService()
|
m := &MockCommand{
|
||||||
c.Name = "ssh"
|
CommandExists: func() error { return nil },
|
||||||
c.State = "present"
|
Executor: func(value any) ([]byte, error) {
|
||||||
yamlData, err := c.Read(context.Background())
|
return nil, nil
|
||||||
|
},
|
||||||
|
Extractor: func(output []byte, target any) error {
|
||||||
|
s.Common.State = "present"
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ReadCommand = (*command.Command)(m)
|
||||||
|
yamlData, err := s.Read(context.Background())
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.YAMLEq(t, yamlResult, string(yamlData))
|
assert.YAMLEq(t, yamlResult, string(yamlData))
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,15 @@ type UserType string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
UserTypeName TypeName = "user"
|
UserTypeName TypeName = "user"
|
||||||
UserTypeAddUser UserType = "adduser"
|
UserTypeBusyBox UserType = "busybox"
|
||||||
UserTypeUserAdd UserType = "useradd"
|
UserTypeShadow UserType = "shadow"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUnsupportedUserType error = errors.New("The UserType is not supported on this system")
|
var ErrUnsupportedUserType error = errors.New("The UserType is not supported on this system")
|
||||||
var ErrInvalidUserType error = errors.New("invalid UserType value")
|
var ErrInvalidUserType error = errors.New("invalid UserType value")
|
||||||
|
|
||||||
|
var SupportedUserTypes []UserType = []UserType{UserTypeShadow, UserTypeBusyBox}
|
||||||
|
|
||||||
var SystemUserType UserType = FindSystemUserType()
|
var SystemUserType UserType = FindSystemUserType()
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
@ -44,48 +46,65 @@ type User struct {
|
|||||||
UID string `json:"uid,omitempty" yaml:"uid,omitempty"`
|
UID string `json:"uid,omitempty" yaml:"uid,omitempty"`
|
||||||
Group string `json:"group,omitempty" yaml:"group,omitempty"`
|
Group string `json:"group,omitempty" yaml:"group,omitempty"`
|
||||||
Groups []string `json:"groups,omitempty" yaml:"groups,omitempty"`
|
Groups []string `json:"groups,omitempty" yaml:"groups,omitempty"`
|
||||||
|
AppendGroups bool `json:"appendgroups,omitempty" yaml:"appendgroups,omitempty"`
|
||||||
Gecos string `json:"gecos,omitempty" yaml:"gecos,omitempty"`
|
Gecos string `json:"gecos,omitempty" yaml:"gecos,omitempty"`
|
||||||
Home string `json:"home" yaml:"home"`
|
Home string `json:"home" yaml:"home"`
|
||||||
CreateHome bool `json:"createhome,omitempty" yaml:"createhome,omitempty"`
|
CreateHome bool `json:"createhome,omitempty" yaml:"createhome,omitempty"`
|
||||||
Shell string `json:"shell,omitempty" yaml:"shell,omitempty"`
|
Shell string `json:"shell,omitempty" yaml:"shell,omitempty"`
|
||||||
UserType UserType `json:"-" yaml:"-"`
|
UserType UserType `json:"type,omitempty" yaml:"type,omitempty"`
|
||||||
|
|
||||||
|
userStatus *user.User `json:"-" yaml:"-"`
|
||||||
|
groupStatus *user.Group `json:"-" yaml:"-"`
|
||||||
|
|
||||||
CreateCommand *command.Command `json:"-" yaml:"-"`
|
CreateCommand *command.Command `json:"-" yaml:"-"`
|
||||||
ReadCommand *command.Command `json:"-" yaml:"-"`
|
ReadCommand *command.Command `json:"-" yaml:"-"`
|
||||||
UpdateCommand *command.Command `json:"-" yaml:"-"`
|
UpdateCommand *command.Command `json:"-" yaml:"-"`
|
||||||
DeleteCommand *command.Command `json:"-" yaml:"-"`
|
DeleteCommand *command.Command `json:"-" yaml:"-"`
|
||||||
config data.ConfigurationValueGetter
|
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUser() *User {
|
func NewUser() *User {
|
||||||
return &User{ CreateHome: true, Common: &Common{ resourceType: UserTypeName } }
|
u := &User{ CreateHome: true, AppendGroups: true, UserType: SystemUserType }
|
||||||
|
u.Common = NewCommon(UserTypeName, false)
|
||||||
|
u.Common.NormalizePath = u.NormalizePath
|
||||||
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"user"}, func(u *url.URL) data.Resource {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"user"}, func(u *url.URL) (user data.Resource) {
|
||||||
user := NewUser()
|
user = NewUser()
|
||||||
user.Name = u.Hostname()
|
if u != nil {
|
||||||
user.UID = LookupUIDString(u.Hostname())
|
if err := folio.CastParsedURI(u).ConstructResource(user); err != nil {
|
||||||
if _, addUserPathErr := exec.LookPath("adduser"); addUserPathErr == nil {
|
panic(err)
|
||||||
user.UserType = UserTypeAddUser
|
}
|
||||||
}
|
}
|
||||||
if _, pathErr := exec.LookPath("useradd"); pathErr == nil {
|
return
|
||||||
user.UserType = UserTypeUserAdd
|
|
||||||
}
|
|
||||||
user.CreateCommand, user.ReadCommand, user.UpdateCommand, user.DeleteCommand = user.UserType.NewCRUD()
|
|
||||||
return user
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func FindSystemUserType() UserType {
|
func FindSystemUserType() UserType {
|
||||||
for _, userType := range []UserType{UserTypeAddUser, UserTypeUserAdd} {
|
for _, userType := range SupportedUserTypes {
|
||||||
c := userType.NewCreateCommand()
|
c := userType.NewCreateCommand()
|
||||||
if c.Exists() {
|
if c.Exists() {
|
||||||
return userType
|
return userType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return UserTypeAddUser
|
return UserTypeShadow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) Init(uri data.URIParser) error {
|
||||||
|
if uri == nil {
|
||||||
|
uri = folio.URI(u.URI()).Parse()
|
||||||
|
} else {
|
||||||
|
u.Name = uri.URL().Hostname()
|
||||||
|
}
|
||||||
|
u.UID = LookupUIDString(uri.URL().Hostname())
|
||||||
|
u.CreateCommand, u.ReadCommand, u.UpdateCommand, u.DeleteCommand = u.UserType.NewCRUD()
|
||||||
|
return u.SetParsedURI(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) NormalizePath() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) SetResourceMapper(resources data.ResourceMapper) {
|
func (u *User) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
@ -121,6 +140,16 @@ func (u *User) Notify(m *machine.EventMessage) {
|
|||||||
switch m.On {
|
switch m.On {
|
||||||
case machine.ENTERSTATEEVENT:
|
case machine.ENTERSTATEEVENT:
|
||||||
switch m.Dest {
|
switch m.Dest {
|
||||||
|
case "start_stat":
|
||||||
|
if statErr := u.ReadStat(); statErr == nil {
|
||||||
|
if triggerErr := u.StateMachine().Trigger("exists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if triggerErr := u.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
case "start_read":
|
case "start_read":
|
||||||
if _,readErr := u.Read(ctx); readErr == nil {
|
if _,readErr := u.Read(ctx); readErr == nil {
|
||||||
if triggerErr := u.StateMachine().Trigger("state_read"); triggerErr == nil {
|
if triggerErr := u.StateMachine().Trigger("state_read"); triggerErr == nil {
|
||||||
@ -133,6 +162,17 @@ func (u *User) Notify(m *machine.EventMessage) {
|
|||||||
u.Common.State = "absent"
|
u.Common.State = "absent"
|
||||||
panic(readErr)
|
panic(readErr)
|
||||||
}
|
}
|
||||||
|
case "start_update":
|
||||||
|
if updateErr := u.Update(ctx); updateErr == nil {
|
||||||
|
if triggerErr := u.stater.Trigger("updated"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
u.Common.State = "absent"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
u.Common.State = "absent"
|
||||||
|
panic(updateErr)
|
||||||
|
}
|
||||||
case "start_delete":
|
case "start_delete":
|
||||||
if deleteErr := u.Delete(ctx); deleteErr == nil {
|
if deleteErr := u.Delete(ctx); deleteErr == nil {
|
||||||
if triggerErr := u.StateMachine().Trigger("deleted"); triggerErr == nil {
|
if triggerErr := u.StateMachine().Trigger("deleted"); triggerErr == nil {
|
||||||
@ -161,27 +201,16 @@ func (u *User) Notify(m *machine.EventMessage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) SetURI(uri string) error {
|
|
||||||
resourceUri, e := url.Parse(uri)
|
|
||||||
if e == nil {
|
|
||||||
if resourceUri.Scheme == "user" {
|
|
||||||
u.Name = resourceUri.Hostname()
|
|
||||||
} else {
|
|
||||||
e = fmt.Errorf("%w: %s is not a user", ErrInvalidResourceURI, uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) URI() string {
|
func (u *User) URI() string {
|
||||||
return fmt.Sprintf("user://%s", u.Name)
|
return fmt.Sprintf("user://%s", u.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) UseConfig(config data.ConfigurationValueGetter) {
|
|
||||||
u.config = config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) ResolveId(ctx context.Context) string {
|
func (u *User) ResolveId(ctx context.Context) string {
|
||||||
|
if u.config != nil {
|
||||||
|
if configUser, configUserErr := u.config.GetValue("user"); configUserErr == nil && u.Name == "self" {
|
||||||
|
u.Name = configUser.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
return LookupUIDString(u.Name)
|
return LookupUIDString(u.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,20 +265,74 @@ func (u *User) Create(ctx context.Context) (error) {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) ReadStat() (err error) {
|
||||||
|
if u.userStatus == nil {
|
||||||
|
if u.userStatus, err = user.Lookup(u.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(u.userStatus.Uid) < 1 {
|
||||||
|
return ErrResourceStateAbsent
|
||||||
|
}
|
||||||
|
u.UID = u.userStatus.Uid
|
||||||
|
if len(u.Group) > 1 {
|
||||||
|
if u.groupStatus == nil {
|
||||||
|
if u.groupStatus, err = user.LookupGroup(u.Group); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(u.groupStatus.Gid) < 1 {
|
||||||
|
return ErrResourceStateAbsent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) Read(ctx context.Context) ([]byte, error) {
|
func (u *User) Read(ctx context.Context) ([]byte, error) {
|
||||||
exErr := u.ReadCommand.Extractor(nil, u)
|
exErr := u.ReadCommand.Extractor(nil, u)
|
||||||
if exErr != nil {
|
if exErr != nil {
|
||||||
u.Common.State = "absent"
|
u.Common.State = "absent"
|
||||||
}
|
}
|
||||||
if yaml, yamlErr := yaml.Marshal(u); yamlErr != nil {
|
_ = u.ReadGroups()
|
||||||
return yaml, yamlErr
|
if yamlDoc, yamlErr := yaml.Marshal(u); yamlErr != nil {
|
||||||
|
return yamlDoc, yamlErr
|
||||||
} else {
|
} else {
|
||||||
return yaml, exErr
|
return yamlDoc, exErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) ReadGroups() (err error) {
|
||||||
|
knownSecondaryGroups := make(map[string]bool)
|
||||||
|
for _, secondaryGroupName := range u.Groups {
|
||||||
|
knownSecondaryGroups[secondaryGroupName] = true
|
||||||
|
}
|
||||||
|
if u.ReadStat() == nil {
|
||||||
|
if groups, groupsErr := u.userStatus.GroupIds(); groupsErr == nil {
|
||||||
|
for _, secondaryGroup := range groups {
|
||||||
|
if readGroup, groupErr := user.LookupGroupId(secondaryGroup); groupErr == nil {
|
||||||
|
if ! knownSecondaryGroups[readGroup.Name] {
|
||||||
|
u.Groups = append(u.Groups, readGroup.Name)
|
||||||
|
knownSecondaryGroups[readGroup.Name] = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = groupErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = groupsErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) Update(ctx context.Context) (error) {
|
func (u *User) Update(ctx context.Context) (error) {
|
||||||
return u.Create(ctx)
|
_, err := u.UpdateCommand.Execute(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_,e := u.Read(ctx)
|
||||||
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) Delete(ctx context.Context) (error) {
|
func (u *User) Delete(ctx context.Context) (error) {
|
||||||
@ -279,38 +362,38 @@ func (u *User) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
|
|
||||||
func (u *UserType) NewCRUD() (create *command.Command, read *command.Command, update *command.Command, del *command.Command) {
|
func (u *UserType) NewCRUD() (create *command.Command, read *command.Command, update *command.Command, del *command.Command) {
|
||||||
switch *u {
|
switch *u {
|
||||||
case UserTypeUserAdd:
|
case UserTypeShadow:
|
||||||
return NewUserAddCreateCommand(), NewUserReadCommand(), NewUserUpdateCommand(), NewUserDelDeleteCommand()
|
return NewUserShadowCreateCommand(), NewUserReadCommand(), NewUserShadowUpdateCommand(), NewUserShadowDeleteCommand()
|
||||||
case UserTypeAddUser:
|
case UserTypeBusyBox:
|
||||||
return NewAddUserCreateCommand(), NewUserReadCommand(), NewUserUpdateCommand(), NewDelUserDeleteCommand()
|
return NewUserBusyBoxCreateCommand(), NewUserReadCommand(), NewUserBusyBoxUpdateCommand(), NewUserBusyBoxDeleteCommand()
|
||||||
default:
|
default:
|
||||||
if _, addUserPathErr := exec.LookPath("adduser"); addUserPathErr == nil {
|
if _, addUserPathErr := exec.LookPath("adduser"); addUserPathErr == nil {
|
||||||
*u = UserTypeAddUser
|
*u = UserTypeBusyBox
|
||||||
return NewAddUserCreateCommand(), NewUserReadCommand(), NewUserUpdateCommand(), NewDelUserDeleteCommand()
|
return NewUserBusyBoxCreateCommand(), NewUserReadCommand(), NewUserBusyBoxUpdateCommand(), NewUserBusyBoxDeleteCommand()
|
||||||
}
|
}
|
||||||
if _, pathErr := exec.LookPath("useradd"); pathErr == nil {
|
if _, pathErr := exec.LookPath("useradd"); pathErr == nil {
|
||||||
*u = UserTypeUserAdd
|
*u = UserTypeShadow
|
||||||
return NewUserAddCreateCommand(), NewUserReadCommand(), NewUserUpdateCommand(), NewUserDelDeleteCommand()
|
return NewUserShadowCreateCommand(), NewUserReadCommand(), NewUserShadowUpdateCommand(), NewUserShadowDeleteCommand()
|
||||||
}
|
}
|
||||||
return NewUserAddCreateCommand(), NewUserReadCommand(), NewUserUpdateCommand(), NewUserDelDeleteCommand()
|
return NewUserShadowCreateCommand(), NewUserReadCommand(), NewUserShadowUpdateCommand(), NewUserShadowDeleteCommand()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *UserType) NewReadCommand() (read *command.Command) {
|
||||||
|
return NewUserReadCommand()
|
||||||
|
}
|
||||||
|
|
||||||
func (u *UserType) NewCreateCommand() (create *command.Command) {
|
func (u *UserType) NewCreateCommand() (create *command.Command) {
|
||||||
switch *u {
|
switch *u {
|
||||||
case UserTypeUserAdd:
|
case UserTypeShadow:
|
||||||
return NewUserAddCreateCommand()
|
return NewUserShadowCreateCommand()
|
||||||
case UserTypeAddUser:
|
case UserTypeBusyBox:
|
||||||
return NewAddUserCreateCommand()
|
return NewUserBusyBoxCreateCommand()
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserType) NewReadCommand() (*command.Command) {
|
|
||||||
return NewUserReadCommand()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *UserType) NewReadUsersCommand() (*command.Command) {
|
func (p *UserType) NewReadUsersCommand() (*command.Command) {
|
||||||
return NewReadUsersCommand()
|
return NewReadUsersCommand()
|
||||||
}
|
}
|
||||||
@ -318,7 +401,7 @@ func (p *UserType) NewReadUsersCommand() (*command.Command) {
|
|||||||
|
|
||||||
func (u *UserType) UnmarshalValue(value string) error {
|
func (u *UserType) UnmarshalValue(value string) error {
|
||||||
switch value {
|
switch value {
|
||||||
case string(UserTypeUserAdd), string(UserTypeAddUser):
|
case string(UserTypeShadow), string(UserTypeBusyBox):
|
||||||
*u = UserType(value)
|
*u = UserType(value)
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
@ -385,7 +468,7 @@ func NewReadUsersCommand() *command.Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserAddCreateCommand() *command.Command {
|
func NewUserShadowCreateCommand() *command.Command {
|
||||||
c := command.NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "useradd"
|
c.Path = "useradd"
|
||||||
c.Args = []command.CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
@ -399,19 +482,11 @@ func NewUserAddCreateCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
c.Extractor = func(out []byte, target any) error {
|
c.Extractor = func(out []byte, target any) error {
|
||||||
return nil
|
return nil
|
||||||
/*
|
|
||||||
for _,line := range strings.Split(string(out), "\n") {
|
|
||||||
if line == "iptables: Chain already exists." {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf(string(out))
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAddUserCreateCommand() *command.Command {
|
func NewUserBusyBoxCreateCommand() *command.Command {
|
||||||
c := command.NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "adduser"
|
c.Path = "adduser"
|
||||||
c.Args = []command.CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
@ -425,14 +500,6 @@ func NewAddUserCreateCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
c.Extractor = func(out []byte, target any) error {
|
c.Extractor = func(out []byte, target any) error {
|
||||||
return nil
|
return nil
|
||||||
/*
|
|
||||||
for _,line := range strings.Split(string(out), "\n") {
|
|
||||||
if line == "iptables: Chain already exists." {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf(string(out))
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
@ -471,11 +538,40 @@ func NewUserReadCommand() *command.Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserUpdateCommand() *command.Command {
|
func NewUserBusyBoxUpdateCommand() *command.Command {
|
||||||
return nil
|
c := command.NewCommand()
|
||||||
|
c.Path = "xargs"
|
||||||
|
c.StdinAvailable = true
|
||||||
|
c.Input = command.CommandInput("{{ if .Groups }}{{ range .Groups }}{{ . }}\n{{ end }}{{ end }}")
|
||||||
|
c.Args = []command.CommandArg{
|
||||||
|
command.CommandArg("adduser"),
|
||||||
|
command.CommandArg("{{ .Name }}"),
|
||||||
|
}
|
||||||
|
c.Extractor = func(out []byte, target any) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserDelDeleteCommand() *command.Command {
|
func NewUserShadowUpdateCommand() *command.Command {
|
||||||
|
c := command.NewCommand()
|
||||||
|
c.Path = "usermod"
|
||||||
|
c.Args = []command.CommandArg{
|
||||||
|
command.CommandArg("{{ if .UID }}-u {{ .UID }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ if .Gecos }}-c {{ .Gecos }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ if .Group }}-g {{ .groupStatus.Gid }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ if .Home }}-d {{ .Home }}{{ if .CreateHome }} -m{{- end }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ if .Groups }}-G {{- range $i, $g := .Groups -}}{{ if $i }}, {{- end }}{{ . }}{{- end }}{{ if .AppendGroups }} -a{{- end }}{{- end }}"),
|
||||||
|
command.CommandArg("{{ if .Shell }}-s {{ .Shell }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ .Name }}"),
|
||||||
|
}
|
||||||
|
c.Extractor = func(out []byte, target any) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserShadowDeleteCommand() *command.Command {
|
||||||
c := command.NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "userdel"
|
c.Path = "userdel"
|
||||||
c.Args = []command.CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
@ -487,7 +583,7 @@ func NewUserDelDeleteCommand() *command.Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDelUserDeleteCommand() *command.Command {
|
func NewUserBusyBoxDeleteCommand() *command.Command {
|
||||||
c := command.NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "deluser"
|
c.Path = "deluser"
|
||||||
c.Args = []command.CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
|
@ -3,16 +3,17 @@ package resource
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "encoding/json"
|
_ "encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
_ "io"
|
_ "io"
|
||||||
_ "net/http"
|
_ "net/http"
|
||||||
_ "net/http/httptest"
|
_ "net/http/httptest"
|
||||||
_ "net/url"
|
_ "net/url"
|
||||||
_ "os"
|
_ "os"
|
||||||
_ "strings"
|
_ "strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"decl/internal/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewUserResource(t *testing.T) {
|
func TestNewUserResource(t *testing.T) {
|
||||||
@ -77,3 +78,43 @@ func TestCreateUser(t *testing.T) {
|
|||||||
applyDeleteErr := u.Apply()
|
applyDeleteErr := u.Apply()
|
||||||
assert.Equal(t, nil, applyDeleteErr)
|
assert.Equal(t, nil, applyDeleteErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSystemUser(t *testing.T) {
|
||||||
|
u := NewUser()
|
||||||
|
u.Name = "self"
|
||||||
|
u.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
|
||||||
|
switch key {
|
||||||
|
case "user":
|
||||||
|
return "bar", nil
|
||||||
|
case "group":
|
||||||
|
return "foo", nil
|
||||||
|
case "uid":
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
|
||||||
|
}))
|
||||||
|
|
||||||
|
assert.Nil(t, u.Init(nil))
|
||||||
|
u.ResolveId(context.Background())
|
||||||
|
|
||||||
|
assert.Equal(t, "bar", u.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSecondaryGroups(t *testing.T) {
|
||||||
|
groupCounts := make(map[string]int)
|
||||||
|
u := NewUser()
|
||||||
|
u.Name = "root"
|
||||||
|
u.ResolveId(context.Background())
|
||||||
|
u.Groups = []string{
|
||||||
|
"root",
|
||||||
|
"wheel",
|
||||||
|
}
|
||||||
|
assert.Nil(t, u.ReadGroups())
|
||||||
|
|
||||||
|
for _, groupName := range u.Groups {
|
||||||
|
groupCounts[groupName]++
|
||||||
|
}
|
||||||
|
assert.Greater(t, len(u.Groups), 2)
|
||||||
|
assert.Equal(t, 1, groupCounts["root"])
|
||||||
|
assert.Equal(t, 1, groupCounts["wheel"])
|
||||||
|
}
|
||||||
|
126
internal/system/os.go
Normal file
126
internal/system/os.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"regexp"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var MatchId *regexp.Regexp = regexp.MustCompile(`^[0-9]+$`)
|
||||||
|
|
||||||
|
func LookupUIDString(userName string) string {
|
||||||
|
user, userLookupErr := user.Lookup(userName)
|
||||||
|
if userLookupErr != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return user.Uid
|
||||||
|
}
|
||||||
|
|
||||||
|
func LookupUID(userName string) (int, error) {
|
||||||
|
var userLookupErr error
|
||||||
|
var UID string
|
||||||
|
if MatchId.MatchString(userName) {
|
||||||
|
user, err := user.LookupId(userName)
|
||||||
|
slog.Info("LookupUID() numeric", "user", user, "userLookupErr", err)
|
||||||
|
if err != nil {
|
||||||
|
userLookupErr = err
|
||||||
|
UID = userName
|
||||||
|
} else {
|
||||||
|
UID = user.Uid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if user, err := user.Lookup(userName); err != nil {
|
||||||
|
return -1, err
|
||||||
|
} else {
|
||||||
|
UID = user.Uid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, uidErr := strconv.Atoi(UID)
|
||||||
|
slog.Info("LookupUID()", "uid", uid, "uidErr", uidErr)
|
||||||
|
if uidErr != nil {
|
||||||
|
if userLookupErr != nil {
|
||||||
|
return -1, userLookupErr
|
||||||
|
} else {
|
||||||
|
return -1, uidErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uid, userLookupErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func LookupGID(groupName string) (int, error) {
|
||||||
|
var GID string
|
||||||
|
if MatchId.MatchString(groupName) {
|
||||||
|
group, groupLookupErr := user.LookupGroupId(groupName)
|
||||||
|
if groupLookupErr != nil {
|
||||||
|
//return -1, groupLookupErr
|
||||||
|
GID = groupName
|
||||||
|
} else {
|
||||||
|
GID = group.Gid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
group, groupLookupErr := user.LookupGroup(groupName)
|
||||||
|
if groupLookupErr != nil {
|
||||||
|
return -1, groupLookupErr
|
||||||
|
}
|
||||||
|
GID = group.Gid
|
||||||
|
}
|
||||||
|
|
||||||
|
gid, gidErr := strconv.Atoi(GID)
|
||||||
|
if gidErr != nil {
|
||||||
|
return -1, gidErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LookupGIDString(groupName string) string {
|
||||||
|
group, groupLookupErr := user.LookupGroup(groupName)
|
||||||
|
if groupLookupErr != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return group.Gid
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessUser() *user.User {
|
||||||
|
processUser, userErr := user.Current()
|
||||||
|
if userErr != nil {
|
||||||
|
panic(userErr)
|
||||||
|
}
|
||||||
|
return processUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessGroup(u *user.User) *user.Group {
|
||||||
|
if u == nil {
|
||||||
|
u = ProcessUser()
|
||||||
|
}
|
||||||
|
processGroup, groupErr := user.LookupGroupId(u.Gid)
|
||||||
|
if groupErr != nil {
|
||||||
|
panic(groupErr)
|
||||||
|
}
|
||||||
|
return processGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessUserName() string {
|
||||||
|
processUser, userErr := user.Current()
|
||||||
|
if userErr != nil {
|
||||||
|
panic(userErr)
|
||||||
|
}
|
||||||
|
return processUser.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessGroupName() string {
|
||||||
|
processUser, userErr := user.Current()
|
||||||
|
if userErr != nil {
|
||||||
|
panic(userErr)
|
||||||
|
}
|
||||||
|
processGroup, groupErr := user.LookupGroupId(processUser.Gid)
|
||||||
|
if groupErr != nil {
|
||||||
|
panic(groupErr)
|
||||||
|
}
|
||||||
|
return processGroup.Name
|
||||||
|
}
|
38
internal/system/os_test.go
Normal file
38
internal/system/os_test.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||||
|
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "context"
|
||||||
|
_ "encoding/json"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
_ "io"
|
||||||
|
_ "net/http"
|
||||||
|
_ "net/http/httptest"
|
||||||
|
_ "net/url"
|
||||||
|
_ "strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLookupUID(t *testing.T) {
|
||||||
|
uid, e := LookupUID("nobody")
|
||||||
|
|
||||||
|
assert.Nil(t, e)
|
||||||
|
assert.Equal(t, 65534, uid)
|
||||||
|
|
||||||
|
nuid, ne := LookupUID("10101")
|
||||||
|
assert.Error(t, ne, "user: unknonwn userid ", ne)
|
||||||
|
assert.Equal(t, 10101, nuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookupGID(t *testing.T) {
|
||||||
|
gid, e := LookupGID("adm")
|
||||||
|
|
||||||
|
assert.Nil(t, e)
|
||||||
|
assert.Equal(t, 4, gid)
|
||||||
|
|
||||||
|
ngid, ne := LookupGID("1001")
|
||||||
|
assert.Nil(t, ne)
|
||||||
|
assert.Equal(t, 1001, ngid)
|
||||||
|
}
|
@ -90,7 +90,11 @@ func (h *HTTP) Stat() (info fs.FileInfo, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode > 399 {
|
||||||
|
err = ErrTransportResourceAbsent
|
||||||
|
return
|
||||||
|
}
|
||||||
fi := NewHTTPFileInfo(uri)
|
fi := NewHTTPFileInfo(uri)
|
||||||
contentLength := response.Header.Get("Content-Length")
|
contentLength := response.Header.Get("Content-Length")
|
||||||
if contentLength != "" {
|
if contentLength != "" {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
package transport
|
package transport
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "errors"
|
"errors"
|
||||||
_ "fmt"
|
_ "fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -15,6 +15,10 @@ _ "os"
|
|||||||
"context"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrTransportResourceAbsent error = errors.New("Transport resource is absent")
|
||||||
|
)
|
||||||
|
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
URI() *url.URL
|
URI() *url.URL
|
||||||
ContentType() string
|
ContentType() string
|
||||||
@ -162,11 +166,17 @@ func (r *Reader) AddHeader(name string, value string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) Status() string {
|
func (r *Reader) Status() string {
|
||||||
return r.handle.(*HTTPReader).GetResponse().Status
|
if httpReader, ok := r.handle.(*HTTPReader); ok {
|
||||||
|
return httpReader.GetResponse().Status
|
||||||
|
}
|
||||||
|
panic("Unable to get HTTP status from reader")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) StatusCode() int {
|
func (r *Reader) StatusCode() int {
|
||||||
return r.handle.(*HTTPReader).GetResponse().StatusCode
|
if httpReader, ok := r.handle.(*HTTPReader); ok {
|
||||||
|
return httpReader.GetResponse().StatusCode
|
||||||
|
}
|
||||||
|
panic("Unable to get HTTP status code from reader")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) Response() *http.Response {
|
func (r *Reader) Response() *http.Response {
|
||||||
|
@ -105,6 +105,19 @@ func (t *Types[Product]) NewFromParsedURI(u *url.URL) (result Product, err error
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns an uninitialized resource for the given type
|
||||||
|
func (t *Types[Product]) NewFromType(typename string) (result Product, err error) {
|
||||||
|
if r, ok := t.registry[typename]; ok {
|
||||||
|
if result = r(nil); result != any(nil) {
|
||||||
|
return result, nil
|
||||||
|
} else {
|
||||||
|
return result, fmt.Errorf("%w: factory failed creating %s", ErrInvalidProduct, typename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%w: %s", ErrUnknownType, typename)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Types[Product]) Has(typename string) bool {
|
func (t *Types[Product]) Has(typename string) bool {
|
||||||
if _, ok := t.registry[typename]; ok {
|
if _, ok := t.registry[typename]; ok {
|
||||||
return true
|
return true
|
||||||
|
@ -61,3 +61,17 @@ func TestTypeName(t *testing.T) {
|
|||||||
assert.Equal(t, "file", string(fTypeName.Name))
|
assert.Equal(t, "file", string(fTypeName.Name))
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
func TestNewFromType(t *testing.T) {
|
||||||
|
testTypes := New[string]()
|
||||||
|
assert.NotNil(t, testTypes)
|
||||||
|
|
||||||
|
testTypes.Register([]string{"foo"}, func(*url.URL) string { return "test" })
|
||||||
|
|
||||||
|
r, e := testTypes.NewFromType("foo")
|
||||||
|
assert.Nil(t, e)
|
||||||
|
assert.Equal(t, "test", r)
|
||||||
|
failr, faile := testTypes.NewFromType("bar")
|
||||||
|
assert.ErrorIs(t, faile, ErrUnknownType)
|
||||||
|
assert.Equal(t, "", failr)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user