Compare commits
No commits in common. "main" and "v0.3.0" have entirely different histories.
8
Makefile
8
Makefile
@ -15,12 +15,6 @@ jx-cli:
|
|||||||
test: jx-cli
|
test: jx-cli
|
||||||
go test -coverprofile=artifacts/coverage.profile ./...
|
go test -coverprofile=artifacts/coverage.profile ./...
|
||||||
go tool cover -html=artifacts/coverage.profile -o artifacts/code-coverage.html
|
go tool cover -html=artifacts/coverage.profile -o artifacts/code-coverage.html
|
||||||
alpine-deps:
|
|
||||||
apk add make
|
|
||||||
apk add golangci-lint
|
|
||||||
apk add git
|
|
||||||
apk add luajit luajit-dev
|
|
||||||
apk add gpg gpg-agent
|
|
||||||
fedora-deps:
|
fedora-deps:
|
||||||
./jx apply build/rpm.jx.yaml
|
./jx apply build/rpm.jx.yaml
|
||||||
spectool -g -R build/jx.spec
|
spectool -g -R build/jx.spec
|
||||||
@ -38,8 +32,6 @@ 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 /tmp:/tmp -v $(HOME)/.gitconfig:/root/.gitconfig -v /var/run/docker.sock:/var/run/docker.sock -e WORKSPACE_PATH=$(shell pwd) -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,9 +10,7 @@ These tools work with YAML descriptions of resources (E.g. files, users, contain
|
|||||||
|
|
||||||
# Releases
|
# Releases
|
||||||
|
|
||||||
**<span style="color:red">v0 releases are unstable and changes may be made to interfaces and specifications.</span>**
|
**v0 releases are unstable and changes may be made to interfaces and specifications.**
|
||||||
|
|
||||||
Use at your own risk.
|
|
||||||
|
|
||||||
# JX Documents
|
# JX Documents
|
||||||
|
|
||||||
@ -43,14 +41,14 @@ resources:
|
|||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
|
|
||||||
Testing the current version involves checking out main and building inside of the alpine go build container.
|
Testing the current version involves checking out main and building.
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://gitea.rosskeen.house/doublejynx/jx.git
|
git clone https://gitea.rosskeen.house/doublejynx/jx.git
|
||||||
|
|
||||||
make build-container
|
|
||||||
|
|
||||||
make test
|
make test
|
||||||
|
|
||||||
|
make build
|
||||||
```
|
```
|
||||||
|
|
||||||
# Command-line
|
# Command-line
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
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
|
|
@ -1,36 +0,0 @@
|
|||||||
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
|
|
@ -1,45 +0,0 @@
|
|||||||
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,25 +184,6 @@ 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(`
|
||||||
@ -234,7 +215,7 @@ resources:
|
|||||||
- type: http
|
- type: http
|
||||||
transition: read
|
transition: read
|
||||||
attributes:
|
attributes:
|
||||||
endpoint: %s/resource/user
|
endpoint: https://gitea.rosskeen.house
|
||||||
- type: route
|
- type: route
|
||||||
transition: read
|
transition: read
|
||||||
attributes:
|
attributes:
|
||||||
@ -246,7 +227,7 @@ resources:
|
|||||||
rtid: all
|
rtid: all
|
||||||
routetype: local
|
routetype: local
|
||||||
metric: 100
|
metric: 100
|
||||||
`, TempDir.FilePath("testread"), server.URL)
|
`, TempDir.FilePath("testread"))
|
||||||
|
|
||||||
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.Warn("Failed loading default configuration", "error", configErr)
|
slog.Error("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/conf.d", "Config file path")
|
cmdFlagSet.StringVar(&ConfigPath, "config", "/etc/jx", "Config file path")
|
||||||
cmdFlagSet.StringVar(&ConfigPath, "c", "/etc/jx/conf.d", "Config file path")
|
cmdFlagSet.StringVar(&ConfigPath, "c", "/etc/jx", "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)")
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
// 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,3 +0,0 @@
|
|||||||
# Import the built-in install document which install the jx binary.
|
|
||||||
imports:
|
|
||||||
- file://documents/install.jx.yaml
|
|
@ -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
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
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
|
|
@ -1,16 +0,0 @@
|
|||||||
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
|
|
@ -100,10 +100,8 @@ func (a *App) SetOutput(uri string) (err error) {
|
|||||||
|
|
||||||
// Each document has an `imports` keyword which can be used to load dependencies
|
// Each document has an `imports` keyword which can be used to load dependencies
|
||||||
func (a *App) LoadDocumentImports() error {
|
func (a *App) LoadDocumentImports() error {
|
||||||
slog.Info("Client.LoadDocumentImports()", "documents", a.Documents)
|
|
||||||
for i, d := range a.Documents {
|
for i, d := range a.Documents {
|
||||||
importedDocs := d.ImportedDocuments()
|
importedDocs := d.ImportedDocuments()
|
||||||
slog.Info("Client.LoadDocumentImports()", "imported", importedDocs)
|
|
||||||
for _, importedDocument := range importedDocs {
|
for _, importedDocument := range importedDocs {
|
||||||
docURI := folio.URI(importedDocument.GetURI())
|
docURI := folio.URI(importedDocument.GetURI())
|
||||||
if _, ok := a.ImportedMap[docURI]; !ok {
|
if _, ok := a.ImportedMap[docURI]; !ok {
|
||||||
@ -128,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().URL()
|
u := resourceURI.Parse()
|
||||||
if u == nil {
|
if u == nil {
|
||||||
return fmt.Errorf("Failed adding resource: %s", uri)
|
return fmt.Errorf("Failed adding resource: %s", uri)
|
||||||
}
|
}
|
||||||
@ -149,19 +147,9 @@ 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 source := folio.URI(uri).Parse().URL(); source != nil {
|
if loadedDocuments, err = folio.DocumentRegistry.Load(folio.URI(uri)); err == nil && loadedDocuments != nil {
|
||||||
if source.Scheme == "" {
|
a.Documents = append(a.Documents, loadedDocuments...)
|
||||||
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
|
||||||
}
|
}
|
||||||
@ -195,7 +183,7 @@ func (a *App) Apply(ctx context.Context, deleteResources bool) (err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Client.Apply()", "uri", d.GetURI(), "document", d, "state", overrideState, "error", err)
|
slog.Info("Client.Apply()", "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
|
||||||
|
@ -1,162 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
"fmt"
|
|
||||||
"context"
|
|
||||||
"decl/internal/folio"
|
|
||||||
"decl/internal/resource"
|
|
||||||
"decl/internal/codec"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var containerDoc string = `
|
|
||||||
imports:
|
|
||||||
- %s
|
|
||||||
resources:
|
|
||||||
- type: container
|
|
||||||
transition: create
|
|
||||||
attributes:
|
|
||||||
image: rosskeenhouse/build-golang:1.22.6-alpine
|
|
||||||
name: jx-client-resources-test
|
|
||||||
hostconfig:
|
|
||||||
autoremove: false
|
|
||||||
mounts:
|
|
||||||
- type: "bind"
|
|
||||||
source: "%s"
|
|
||||||
target: "/src"
|
|
||||||
- type: "bind"
|
|
||||||
source: "%s"
|
|
||||||
target: "%s"
|
|
||||||
workingdir: "/src"
|
|
||||||
entrypoint:
|
|
||||||
- "/src/jx"
|
|
||||||
cmd:
|
|
||||||
- apply
|
|
||||||
- %s
|
|
||||||
wait: true
|
|
||||||
---
|
|
||||||
resources:
|
|
||||||
- type: container
|
|
||||||
transition: delete
|
|
||||||
attributes:
|
|
||||||
name: jx-client-resources-test
|
|
||||||
`
|
|
||||||
|
|
||||||
// create a container
|
|
||||||
// run a test inside the container
|
|
||||||
func TestUserResource(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
c := NewClient()
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
|
|
||||||
TempDir.Mkdir("testresources", 0700)
|
|
||||||
tmpresourcespath := TempDir.FilePath("testresources")
|
|
||||||
configurations := fmt.Sprintf(`
|
|
||||||
configurations:
|
|
||||||
- name: tmpdir
|
|
||||||
values:
|
|
||||||
prefix: %s
|
|
||||||
`, tmpresourcespath)
|
|
||||||
|
|
||||||
assert.Nil(t, TempDir.CreateFile("config.jx.yaml", configurations))
|
|
||||||
|
|
||||||
configURI := TempDir.URIPath("config.jx.yaml")
|
|
||||||
//assert.Nil(t, c.Import([]string{configURI}))
|
|
||||||
|
|
||||||
testUserFile := fmt.Sprintf(`
|
|
||||||
imports:
|
|
||||||
- %s
|
|
||||||
resources:
|
|
||||||
- type: file
|
|
||||||
config: tmpdir
|
|
||||||
transition: update
|
|
||||||
attributes:
|
|
||||||
path: testdir
|
|
||||||
mode: 0600
|
|
||||||
state: present
|
|
||||||
- type: group
|
|
||||||
transition: update
|
|
||||||
attributes:
|
|
||||||
name: testuser
|
|
||||||
- type: group
|
|
||||||
transition: update
|
|
||||||
attributes:
|
|
||||||
name: testgroup
|
|
||||||
- type: user
|
|
||||||
transition: update
|
|
||||||
attributes:
|
|
||||||
name: testuser
|
|
||||||
gecos: "my test account"
|
|
||||||
home: "/home/testuser"
|
|
||||||
createhome: true
|
|
||||||
group: testuser
|
|
||||||
groups:
|
|
||||||
- testgroup
|
|
||||||
- testuser
|
|
||||||
appendgroups: true
|
|
||||||
state: present
|
|
||||||
`, configURI)
|
|
||||||
|
|
||||||
assert.Nil(t, TempDir.CreateFile("test_userfile.jx.yaml", testUserFile))
|
|
||||||
|
|
||||||
for _, resourceTestDoc := range []string{
|
|
||||||
TempDir.FilePath("test_userfile.jx.yaml"),
|
|
||||||
} {
|
|
||||||
content := fmt.Sprintf(containerDoc, configURI, os.Getenv("WORKSPACE_PATH"), TempDir, TempDir, resourceTestDoc)
|
|
||||||
assert.Nil(t, TempDir.CreateFile("run-tests.jx.yaml", content))
|
|
||||||
|
|
||||||
runTestsDocument := TempDir.URIPath("run-tests.jx.yaml")
|
|
||||||
assert.Nil(t, c.Import([]string{runTestsDocument}))
|
|
||||||
assert.Nil(t, c.LoadDocumentImports())
|
|
||||||
|
|
||||||
assert.Nil(t, c.Apply(ctx, false))
|
|
||||||
|
|
||||||
applied, ok := folio.DocumentRegistry.GetDocument(folio.URI(runTestsDocument))
|
|
||||||
assert.True(t, ok)
|
|
||||||
|
|
||||||
cont := applied.ResourceDeclarations[0].Resource().(*resource.Container)
|
|
||||||
|
|
||||||
slog.Info("TestUserResources", "stdout", cont.Stdout, "stderr", cont.Stderr)
|
|
||||||
slog.Info("TestUserResources", "doc", applied, "container", applied.ResourceDeclarations[0])
|
|
||||||
assert.Equal(t, 0, len(applied.Errors))
|
|
||||||
assert.Greater(t, len(cont.Stdout), 0)
|
|
||||||
|
|
||||||
resultReader := io.NopCloser(strings.NewReader(cont.Stdout))
|
|
||||||
decoder := codec.NewDecoder(resultReader, codec.FormatYaml)
|
|
||||||
|
|
||||||
result := folio.NewDocument(nil)
|
|
||||||
assert.Nil(t, decoder.Decode(folio.NewDocument(nil)))
|
|
||||||
assert.Nil(t, decoder.Decode(result))
|
|
||||||
|
|
||||||
uri := fmt.Sprintf("file://%s", resourceTestDoc)
|
|
||||||
//testDoc := folio.DocumentRegistry.NewDocument(folio.URI(uri))
|
|
||||||
docs, loadErr := folio.DocumentRegistry.Load(folio.URI(uri))
|
|
||||||
assert.Nil(t, loadErr)
|
|
||||||
testDoc := docs[0]
|
|
||||||
|
|
||||||
var added int = 0
|
|
||||||
diffs, diffsErr := testDoc.(*folio.Document).Diff(result, nil)
|
|
||||||
assert.Nil(t, diffsErr)
|
|
||||||
assert.Greater(t, len(diffs), 1)
|
|
||||||
for _, line := range strings.Split(diffs, "\n") {
|
|
||||||
if len(line) > 0 {
|
|
||||||
switch line[0] {
|
|
||||||
case '+':
|
|
||||||
slog.Info("TestUserResources Diff", "line", line, "added", added)
|
|
||||||
added++
|
|
||||||
case '-':
|
|
||||||
assert.Fail(t, "resource attribute missing", line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert.Equal(t, 4, added)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -3,56 +3,40 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "context"
|
_ "context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"errors"
|
"errors"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
_ "net/url"
|
_ "net/url"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A resource that implements the ExecProvider interface can be used as an exec target.
|
|
||||||
type CommandProvider interface {
|
|
||||||
Start() error
|
|
||||||
Wait() error
|
|
||||||
SetCmdEnv([]string)
|
|
||||||
SetStdin(io.Reader)
|
|
||||||
StdinPipe() (io.WriteCloser, error)
|
|
||||||
StdoutPipe() (io.ReadCloser, error)
|
|
||||||
StderrPipe() (io.ReadCloser, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrUnknownCommand error = errors.New("Unable to find command in path")
|
var ErrUnknownCommand error = errors.New("Unable to find command in path")
|
||||||
|
|
||||||
type CommandExecutor func(value any) ([]byte, error)
|
type CommandExecutor func(value any) ([]byte, error)
|
||||||
type CommandExtractAttributes func(output []byte, target any) error
|
type CommandExtractAttributes func(output []byte, target any) error
|
||||||
type CommandExists func() error
|
type CommandExists func() error
|
||||||
|
|
||||||
type CommandInput string
|
type CommandArg 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"`
|
||||||
ExitCode int `json:"exitcode,omitempty" yaml:"exitcode,omitempty"`
|
Executor CommandExecutor `json:"-" yaml:"-"`
|
||||||
Stdout string `json:"stdout,omitempty" yaml:"stdout,omitempty"`
|
Extractor CommandExtractAttributes `json:"-" yaml:"-"`
|
||||||
Stderr string `json:"stderr,omitempty" yaml:"stderr,omitempty"`
|
CommandExists CommandExists `json:"-" yaml:"-"`
|
||||||
Executor CommandExecutor `json:"-" yaml:"-"`
|
stdin io.Reader `json:"-" yaml:"-"`
|
||||||
Extractor CommandExtractAttributes `json:"-" yaml:"-"`
|
|
||||||
CommandExists CommandExists `json:"-" yaml:"-"`
|
|
||||||
Input CommandInput `json:"-" yaml:"-"`
|
|
||||||
stdin io.Reader `json:"-" yaml:"-"`
|
|
||||||
|
|
||||||
TargetRef CommandTargetRef `json:"targetref,omitempty" yaml:"targetref,omitempty"`
|
|
||||||
execHandle CommandProvider `json:"-" yaml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommand() *Command {
|
func NewCommand() *Command {
|
||||||
@ -61,14 +45,7 @@ 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 {
|
||||||
@ -78,21 +55,18 @@ 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)
|
||||||
|
if err != nil {
|
||||||
c.execHandle = c.TargetRef.Provider(c, value)
|
return nil, err
|
||||||
|
|
||||||
if inputErr := c.SetInput(value); inputErr != nil {
|
|
||||||
return nil, inputErr
|
|
||||||
}
|
}
|
||||||
|
cmd := exec.Command(c.Path, args...)
|
||||||
|
c.SetCmdEnv(cmd)
|
||||||
|
|
||||||
c.SetCmdEnv()
|
|
||||||
|
|
||||||
cmd := c.execHandle
|
|
||||||
if c.stdin != nil {
|
if c.stdin != nil {
|
||||||
cmd.SetStdin(c.stdin)
|
cmd.Stdin = c.stdin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slog.Info("execute() - cmd", "path", c.Path, "args", args)
|
||||||
output, stdoutPipeErr := cmd.StdoutPipe()
|
output, stdoutPipeErr := cmd.StdoutPipe()
|
||||||
if stdoutPipeErr != nil {
|
if stdoutPipeErr != nil {
|
||||||
return nil, stdoutPipeErr
|
return nil, stdoutPipeErr
|
||||||
@ -117,17 +91,11 @@ 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 {
|
||||||
slog.Info("execute()", "path", c.Path, "args", args, "output", string(stdOutOutput), "error", string(stdErrOutput))
|
slog.Info("execute()", "path", c.Path, "args", args, "output", string(stdOutOutput), "error", string(stdErrOutput))
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
if len(stdErrOutput) > 0 && c.FailOnError {
|
if len(stdErrOutput) > 0 && c.FailOnError {
|
||||||
return stdOutOutput, fmt.Errorf("%w %s", waitErr, string(stdErrOutput))
|
return stdOutOutput, fmt.Errorf("%w %s", waitErr, string(stdErrOutput))
|
||||||
@ -144,8 +112,8 @@ func (c *Command) LoadDecl(yamlResourceDeclaration string) error {
|
|||||||
return codec.NewYAMLStringDecoder(yamlResourceDeclaration).Decode(c)
|
return codec.NewYAMLStringDecoder(yamlResourceDeclaration).Decode(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) SetCmdEnv() {
|
func (c *Command) SetCmdEnv(cmd *exec.Cmd) {
|
||||||
c.execHandle.SetCmdEnv(c.Env)
|
cmd.Env = append(os.Environ(), c.Env...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) SetStdinReader(r io.Reader) {
|
func (c *Command) SetStdinReader(r io.Reader) {
|
||||||
@ -158,15 +126,6 @@ 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 {
|
||||||
@ -195,20 +154,23 @@ func (c *Command) Execute(value any) ([]byte, error) {
|
|||||||
return c.Executor(value)
|
return c.Executor(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) SetInput(value any) error {
|
func (c *CommandArg) UnmarshalValue(value string) error {
|
||||||
if len(c.Input) > 0 {
|
*c = CommandArg(value)
|
||||||
if r, err := c.Input.Template(value); err != nil {
|
|
||||||
slog.Info("Command.SetInput", "input", r.String(), "error", err)
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
slog.Info("Command.SetInput", "input", r.String())
|
|
||||||
c.SetStdinReader(strings.NewReader(r.String()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandInput) Template(value any) (result strings.Builder, err error) {
|
func (c *CommandArg) UnmarshalJSON(data []byte) error {
|
||||||
err = template.Must(template.New("commandInput").Parse(string(*c))).Execute(&result, value)
|
var s string
|
||||||
return
|
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)
|
||||||
}
|
}
|
||||||
|
@ -81,22 +81,3 @@ 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)
|
|
||||||
}
|
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommandArg string
|
|
||||||
|
|
||||||
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,30 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"decl/internal/identifier"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommandTargetRef identifier.ID
|
|
||||||
|
|
||||||
func (r CommandTargetRef) GetType() (CommandType) {
|
|
||||||
uri := identifier.ID(r).Parse()
|
|
||||||
if uri != nil {
|
|
||||||
var ct CommandType = CommandType(uri.Scheme)
|
|
||||||
if ct.Validate() == nil {
|
|
||||||
return ct
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r CommandTargetRef) Provider(cmd *Command, value any) CommandProvider {
|
|
||||||
return r.GetType().Provider(cmd, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r CommandTargetRef) Name() string {
|
|
||||||
u := identifier.ID(r).Parse()
|
|
||||||
return filepath.Join(u.Hostname(), u.Path)
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidCommandType error = errors.New("Invalid command type")
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommandType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ExecCommand CommandType = "exec"
|
|
||||||
ContainerCommand CommandType = "container"
|
|
||||||
SSHCommand CommandType = "ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func (t CommandType) Validate() error {
|
|
||||||
switch t {
|
|
||||||
case ExecCommand, ContainerCommand, SSHCommand:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w: %s", ErrInvalidCommandType, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t CommandType) Provider(cmd *Command, value any) CommandProvider {
|
|
||||||
switch t {
|
|
||||||
case ContainerCommand:
|
|
||||||
return NewContainerProvider(cmd, value)
|
|
||||||
case SSHCommand:
|
|
||||||
return NewSSHProvider(cmd, value)
|
|
||||||
default:
|
|
||||||
fallthrough
|
|
||||||
case ExecCommand:
|
|
||||||
return NewExecProvider(cmd, value)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
|
|
||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCommandType(t *testing.T) {
|
|
||||||
|
|
||||||
var cmdType CommandType = "container"
|
|
||||||
|
|
||||||
assert.Nil(t, cmdType.Validate())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
|
|
||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "fmt"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
_ "os"
|
|
||||||
_ "strings"
|
|
||||||
"testing"
|
|
||||||
"bytes"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
func TestNewContainerProvider(t *testing.T) {
|
|
||||||
c := NewContainerProvider(nil, nil)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func TestContainerCommandLoad(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 TestContainerCommandTemplate(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 := struct { Path string } {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainerCommandStdin(t *testing.T) {
|
|
||||||
var expected string = "stdin test data"
|
|
||||||
var stdinBuffer bytes.Buffer
|
|
||||||
stdinBuffer.WriteString(expected)
|
|
||||||
|
|
||||||
c := NewCommand()
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
|
|
||||||
decl := `
|
|
||||||
path: cat
|
|
||||||
stdinavailable: true
|
|
||||||
`
|
|
||||||
|
|
||||||
assert.Nil(t, c.LoadDecl(decl))
|
|
||||||
assert.Equal(t, "cat", c.Path)
|
|
||||||
|
|
||||||
c.SetStdinReader(&stdinBuffer)
|
|
||||||
out, err := c.Execute(nil)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, expected, string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainerCommandExitCode(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)
|
|
||||||
}
|
|
@ -1,142 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"decl/internal/containerlog"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/strslice"
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"time"
|
|
||||||
"log/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ContainerExecClient interface {
|
|
||||||
ContainerExecAttach(ctx context.Context, execID string, config container.ExecAttachOptions) (types.HijackedResponse, error)
|
|
||||||
ContainerExecCreate(ctx context.Context, containerID string, options container.ExecOptions) (types.IDResponse, error)
|
|
||||||
ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error)
|
|
||||||
ContainerExecStart(ctx context.Context, execID string, config container.ExecStartOptions) (error)
|
|
||||||
ContainerList(context.Context, container.ListOptions) ([]types.Container, error)
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContainerCommandProvider struct {
|
|
||||||
containerID string
|
|
||||||
execID string
|
|
||||||
ExitCode int
|
|
||||||
container.ExecOptions
|
|
||||||
response types.HijackedResponse
|
|
||||||
pipes *containerlog.StreamReader
|
|
||||||
Stdin io.Reader
|
|
||||||
apiClient ContainerExecClient `json:"-" yaml:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ref could be a resource, but it just needs to implement the ExecResource interface
|
|
||||||
func NewContainerProvider(cmd *Command, value any) (p *ContainerCommandProvider) {
|
|
||||||
if args, err := cmd.Template(value); err == nil {
|
|
||||||
p = &ContainerCommandProvider {
|
|
||||||
ExecOptions: container.ExecOptions {
|
|
||||||
Cmd: strslice.StrSlice(append([]string{cmd.Path}, args...)),
|
|
||||||
AttachStdin: true,
|
|
||||||
AttachStdout: true,
|
|
||||||
AttachStderr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
p.containerID = p.ResolveId(context.Background(), cmd.TargetRef)
|
|
||||||
slog.Info("command.NewContainerProvider", "command", cmd.Path, "args", args, "target", cmd.TargetRef, "container", p.containerID)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerCommandProvider) ResolveId(ctx context.Context, ref CommandTargetRef) (containerID string) {
|
|
||||||
name := ref.Name()
|
|
||||||
filterArgs := filters.NewArgs()
|
|
||||||
filterArgs.Add("name", "/"+name)
|
|
||||||
containers, listErr := c.apiClient.ContainerList(ctx, container.ListOptions{
|
|
||||||
All: true,
|
|
||||||
Filters: filterArgs,
|
|
||||||
})
|
|
||||||
|
|
||||||
if listErr != nil {
|
|
||||||
panic(listErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, container := range containers {
|
|
||||||
for _, containerName := range container.Names {
|
|
||||||
if containerName == "/"+name {
|
|
||||||
containerID = container.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerCommandProvider) Start() (err error) {
|
|
||||||
var execIDResponse types.IDResponse
|
|
||||||
ctx := context.Background()
|
|
||||||
if execIDResponse, err = c.apiClient.ContainerExecCreate(ctx, c.containerID, c.ExecOptions); err == nil {
|
|
||||||
c.execID = execIDResponse.ID
|
|
||||||
if c.execID == "" {
|
|
||||||
return fmt.Errorf("Failed creating a container exec ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
execStartCheck := types.ExecStartCheck{
|
|
||||||
Tty: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.response, err = c.apiClient.ContainerExecAttach(ctx, c.execID, execStartCheck); err == nil {
|
|
||||||
c.pipes = containerlog.NewStreamReader(c.response.Conn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerCommandProvider) Wait() (err error) {
|
|
||||||
var containerDetails container.ExecInspect
|
|
||||||
ctx := context.Background()
|
|
||||||
for {
|
|
||||||
// copy Stdin to the connection
|
|
||||||
if c.Stdin != nil {
|
|
||||||
io.Copy(c.response.Conn, c.Stdin)
|
|
||||||
}
|
|
||||||
|
|
||||||
if containerDetails, err = c.apiClient.ContainerExecInspect(ctx, c.execID); err != nil || ! containerDetails.Running {
|
|
||||||
c.ExitCode = containerDetails.ExitCode
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerCommandProvider) SetCmdEnv(env []string) {
|
|
||||||
c.Env = append(os.Environ(), env...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerCommandProvider) SetStdin(r io.Reader) {
|
|
||||||
c.Stdin = r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerCommandProvider) StdinPipe() (io.WriteCloser, error) {
|
|
||||||
return c.response.Conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerCommandProvider) StdoutPipe() (io.ReadCloser, error) {
|
|
||||||
return c.pipes.StdoutPipe(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerCommandProvider) StderrPipe() (io.ReadCloser, error) {
|
|
||||||
return c.pipes.StderrPipe(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerCommandProvider) Close() (err error) {
|
|
||||||
err = c.response.CloseWrite()
|
|
||||||
c.response.Close()
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
)
|
|
||||||
|
|
||||||
type ExecCommandProvider struct {
|
|
||||||
*exec.Cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consturct a new exec
|
|
||||||
func NewExecProvider(c *Command, value any) *ExecCommandProvider {
|
|
||||||
if args, err := c.Template(value); err == nil {
|
|
||||||
slog.Info("command.NewExecProvider", "command", c.Path, "args", args, "target", c.TargetRef)
|
|
||||||
return &ExecCommandProvider{exec.Command(c.Path, args...)}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ExecCommandProvider) SetCmdEnv(env []string) {
|
|
||||||
e.Cmd.Env = append(os.Environ(), env...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ExecCommandProvider) SetStdin(r io.Reader) {
|
|
||||||
e.Cmd.Stdin = r
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"log/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
)
|
|
||||||
|
|
||||||
type SSHCommandProvider struct {
|
|
||||||
Env []string
|
|
||||||
Stdin io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consturct a new ssh exec
|
|
||||||
func NewSSHProvider(c *Command, value any) (p *SSHCommandProvider) {
|
|
||||||
if args, err := c.Template(value); err == nil {
|
|
||||||
slog.Info("command.NewSSHProvider", "command", c.Path, "args", args, "target", c.TargetRef)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHCommandProvider) Start() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHCommandProvider) Wait() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHCommandProvider) StdinPipe() (w io.WriteCloser, err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHCommandProvider) StdoutPipe() (r io.ReadCloser, err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHCommandProvider) StderrPipe() (r io.ReadCloser, err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHCommandProvider) SetCmdEnv(env []string) {
|
|
||||||
s.Env = append(os.Environ(), env...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSHCommandProvider) SetStdin(r io.Reader) {
|
|
||||||
s.Stdin = r
|
|
||||||
}
|
|
||||||
|
|
@ -37,7 +37,7 @@ func (c *Certificate) SetURI(uri string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Certificate) SetParsedURI(uri data.URIParser) error {
|
func (c *Certificate) SetParsedURI(uri *url.URL) 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 data.URIParser) error {
|
func (x *Exec) SetParsedURI(uri *url.URL) 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 data.URIParser) error {
|
func (g *Generic[Value]) SetParsedURI(uri *url.URL) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,134 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
|
||||||
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"strconv"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"decl/internal/data"
|
|
||||||
"decl/internal/codec"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OpenPGPKeyRing struct {
|
|
||||||
Keyring string `json:"keyring,omitempty" yaml:"keyring,omitempty"`
|
|
||||||
entities openpgp.EntityList
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOpenPGPKeyRing() *OpenPGPKeyRing {
|
|
||||||
o := &OpenPGPKeyRing{}
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) URI() string {
|
|
||||||
return fmt.Sprintf("%s://%s", o.Type(), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) SetURI(uri string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) SetParsedURI(uri data.URIParser) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) Read(ctx context.Context) (yamlData []byte, err error) {
|
|
||||||
pemReader := io.NopCloser(strings.NewReader(o.Keyring))
|
|
||||||
o.entities, err = openpgp.ReadArmoredKeyRing(pemReader)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) Load(r io.Reader) (err error) {
|
|
||||||
err = codec.NewYAMLDecoder(r).Decode(o)
|
|
||||||
if err == nil {
|
|
||||||
_, err = o.Read(context.Background())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) LoadYAML(yamlData string) (err error) {
|
|
||||||
err = codec.NewYAMLStringDecoder(yamlData).Decode(o)
|
|
||||||
slog.Info("OpenPGP.LoadYAML()", "keyring", len(o.Keyring), "err", err)
|
|
||||||
if err == nil {
|
|
||||||
_, err = o.Read(context.Background())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) UnmarshalJSON(data []byte) error {
|
|
||||||
if unmarshalErr := json.Unmarshal(data, o); unmarshalErr != nil {
|
|
||||||
return unmarshalErr
|
|
||||||
}
|
|
||||||
//o.NewReadConfigCommand()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) UnmarshalYAML(value *yaml.Node) error {
|
|
||||||
type decodeOpenPGP OpenPGPKeyRing
|
|
||||||
if unmarshalErr := value.Decode((*decodeOpenPGP)(o)); unmarshalErr != nil {
|
|
||||||
return unmarshalErr
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) Clone() data.Configuration {
|
|
||||||
jsonGeneric, _ := json.Marshal(o)
|
|
||||||
clone := NewOpenPGPKeyRing()
|
|
||||||
if unmarshalErr := json.Unmarshal(jsonGeneric, &clone); unmarshalErr != nil {
|
|
||||||
panic(unmarshalErr)
|
|
||||||
}
|
|
||||||
return clone
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) Type() string {
|
|
||||||
return "openpgp"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) GetEntityIndex(key string) (index int, field string, err error) {
|
|
||||||
values := strings.SplitN(key, ".", 2)
|
|
||||||
if len(values) == 2 {
|
|
||||||
if index, err = strconv.Atoi(values[0]); err == nil {
|
|
||||||
field = values[1]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = data.ErrUnknownConfigurationKey
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) GetValue(name string) (result any, err error) {
|
|
||||||
index, field, err := o.GetEntityIndex(name)
|
|
||||||
if len(o.entities) > index && err == nil {
|
|
||||||
switch field {
|
|
||||||
case "PublicKey":
|
|
||||||
return o.entities[index].PrimaryKey, err
|
|
||||||
case "PrivateKey":
|
|
||||||
return o.entities[index].PrimaryKey, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = data.ErrUnknownConfigurationKey
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Expected key: 0.PrivateKey
|
|
||||||
func (o *OpenPGPKeyRing) Has(key string) (ok bool) {
|
|
||||||
index, field, err := o.GetEntityIndex(key)
|
|
||||||
if len(o.entities) > index && err == nil {
|
|
||||||
switch field {
|
|
||||||
case "PublicKey":
|
|
||||||
ok = o.entities[index].PrimaryKey != nil
|
|
||||||
case "PrivateKey":
|
|
||||||
ok = o.entities[index].PrimaryKey != nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
privateKey = `
|
|
||||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
|
||||||
|
|
||||||
xcMGBGhI/VUBCADDVSm3mKY5JsncMMJFV0zELMrmip7dkK3vvMvVWVmMHiC4akDH
|
|
||||||
WPxUdWNQPjE2e5HGF9Ebg0c7gu634VG470MnTzFdPV6f5zA+yJfdKrLq7gpl9QGW
|
|
||||||
jFLIeK/l4xc+MIpOE1rD9WqYUUw2IYY8YANYq4yB36rq41VuZps/adI9Go5IhfcU
|
|
||||||
3SVb7o7pa/gWE0FVu9ze31j2agC8FIKHgB++7bmYgbAQz5Qi1qgtG0Kn25QUacJ6
|
|
||||||
Akm2+h4w3SQCR6HLRV2BO29x9mFBszf2KQ7DW2VNiGyUuQQ3m8v2ZidG/11ff6U6
|
|
||||||
ad5tvr/8sYr5jOnKEJyDP9v9yQ04cU94GmsPABEBAAH+CQMIljE6AMIbuXNgXPhS
|
|
||||||
/aEINY2LCOvNUhTUGcepN5zlRJSqGmHCZJ4sI5TWvOzNM4ZCdjQsYYbZhXz5i+SW
|
|
||||||
R+YeoJKrI/c3jCsazgCUaBqjdHvTi/rHXT77SEQ2c1wBfXmYUbWPpyKeWu31nSnj
|
|
||||||
3vZCLtwoyWtCuR2lWbHtYu6hJu+wTm6chGxiBdCKEOKXCx9ZIiVKYvZE93tSITDX
|
|
||||||
R47rVUpMIt46m0tOr4CbsLjpsbAo6izviqFCMQblHr8kk31IF6yhAnwIfcGr0y3j
|
|
||||||
zzlEY5ntyUqBD6Gwth1wAboWSD4nupq7wRh/TJXes++udR2rPR05lg1HYVbmBvSt
|
|
||||||
03VGk5WQGhFjixU1LxKir7KMJOnDyMxGShTrx/GIhPpG0srWHLJhQtQ+yP0PrlVk
|
|
||||||
ho7JrhBNUbf9uCjSPSVCclgk1JrYNEDcwtitBnwR7QU2bkRQU3VhYjiesRcmTeSg
|
|
||||||
PQttdZoB8aCNfiXlLXb2GnacI49XbH+W4B0HgwqZ4dYSuri37BOm9Gvt9hoZGgsE
|
|
||||||
fdPt//Oox1N0tkwN+j3aLaOkmJSLlzarVlV3A+j3mkY336WalCRd6HFe3RrEgkVH
|
|
||||||
53M2dAdbhNlZAlKOwpsiUGwDFX4AiuWJuqXUpoVt4KTRuoYdVg9B0aXW67WM9eai
|
|
||||||
T9oyur9hZnRy7QANhzuU6m3FBy2EOWHn3c87axK+o48mGDxDYm9PlhIXGbZ7Vb3g
|
|
||||||
diCE2SkiPerZ0Cx0yO3egUy8BIWHdNWdyDYtcGiup8A7WOyF8ftUCynQkdldtYWx
|
|
||||||
5HFlcpiV3o/5C5lkUMMHF72fNOyWwz3PCpLO1uOn+T+jtylkrEY8061SlBF91HA/
|
|
||||||
jaLF6U136VTS1hIj9fjzBhk5a6/43Bk41hhgD0JrVFRFM+S9JIdmwQO1FN41QL1i
|
|
||||||
xKvQOWOE2s1bzS9UZXN0VXNlcjEgKFRlc3RVc2VyMSkgPHRlc3R1c2VyQHJvc3Nr
|
|
||||||
ZWVuLmhvdXNlPsLAigQTAQgAPgUCaEj9VQkQ3weSNhbTZdcWIQQAzpwPAGodxV8p
|
|
||||||
f5vfB5I2FtNl1wIbAwIeAQIZAQILBwIVCAMWAAIDJwcCAABiiwf/cIcIHy5KE2CP
|
|
||||||
kNE3trmV5exT8ltLeLW1EqCNWolcPspjQ1fsLqmLI2A6G9jMiv0wBZEgqmYQSEWx
|
|
||||||
i0SbcM0MJBf2phrnpR0kgV9y3cSv7KdlFPs7/zQRD9S5VDSnIbsaXQT2Iyh3Wziz
|
|
||||||
6CqhX3Qro0saVkyHigsL5w7bj/j5bI1IHGn8TMfnFcHu+wGdRYuQgQ/Q6+5zbC6L
|
|
||||||
8sK0orM8lYeXk9KW3LvBJX+aGEDU9at5rGqq8PebIZRkIsFYK3070Qg3mZymnx8D
|
|
||||||
FKIePKLbDSPWktB6QoYviTXfjy3v1pbI+cfqtLB3Puf9uM0LzdFsfXzxOP+8/D6/
|
|
||||||
glIQMYyO4cfDBgRoSP1VAQgAn2oaIthkcTnzqieGTfE3vuGyXjkHc06HwIrHwlO9
|
|
||||||
+qvjLxCrQLmO9r81nREVqZ+1wAZrZHyCPF8smvIhyrhBghE8tGKGdeRiwYEh4S55
|
|
||||||
TdlP+AZK1Ixr1I2VqrlttoHxQdavGXUhsKYPIa7KWP2/p5wBnWsKvoXxOmEUE5hu
|
|
||||||
bZAa3LcX8YJQZys3s5i9Wt2q0x1n9kkZS3gFjSOLAAq8j/+aZQ+vvWfWF0yng5V5
|
|
||||||
4GugcwfNMuxdoNHD3bV9tHVgBseIq6tiNksZQAb1jGa3ZtlKa+sixw9s06W39RZC
|
|
||||||
hVMPY9Jay9XzKtP4/c2yibE4egrTpRdOLAHqBFfUmd+hrwARAQAB/gkDCAAuxoQ+
|
|
||||||
wVtrYFWspVOMEjwr+2KBmNGJhv6lmsR7C8oauG3W2tz5EUbNz40k+hR+Plft5CuD
|
|
||||||
s5OwMsKJIRcnFOqTqGf9KhF74yDAzOem0cmxR+XKzhBhgcnj2fGoOMQqN4XnAVFG
|
|
||||||
B39p4JK+9IkkHCDefHdXZ6EOpjpmaPL41EmO/l02WOhgW9x69waSLpNlDK1YI7gH
|
|
||||||
72Zhr5BACkv3QWizzU3DP//XQaFyzpjKI01q6f+IXonFkaOiPJXP8Ym4ZAA5FXMF
|
|
||||||
xZl4V0qpsPyvy1PXx7O6NWG0CqV0LpJwsTf2HFXwnnEniZGB3MZqCq1ORoKHsIQe
|
|
||||||
Q27iFhqSM0iYHPL/iRt/TRwYgW3NZwpUh/OtiSMRy32BeQ2SMKocHPrqQsZvPYgF
|
|
||||||
KdZVvpu3n/n8Lj8Wtx+89vz28kd5HG6M01HmE8PDdRp4lryH/pPJb0I/W4TRzQgv
|
|
||||||
ZrWxP8BZPvLiyOxv+74lvV0gr+0zar8jU9RvhsbN/Nt/PU0dl4794K38Xo/vppAQ
|
|
||||||
GaGFjlQ3he3Vnb5wNA3hUIaBlOGihd28t6Jf3T+oqmfhYtZ95G7Q/8zvCOVadfUf
|
|
||||||
5j4xb3LCQYfXNTwDgbGzivpAkje33nX22r38uJg+yeGb8BskMzZWeZMztkw8ia44
|
|
||||||
F94D9dxtSa++6VQ97uKxzTza37876YDr4I6LVKu+JVIj4pp8FLS1ebuZC1HngJCB
|
|
||||||
RO2Ipx9zLIV9Pf4AqH0JW93WomTBnc927EdIeA7EZlybdif4kiRF4hONUIjMcGbt
|
|
||||||
PBbQbpDlc+ZWJcz7UtJTn9TyUwE0B7oMogV4sGM89jrtlH+BqOwLM1QvpuDTmn3b
|
|
||||||
eXZn+lZpHm174kN/VMaztxkvuxsmZRemMiHs7k1mAD7umDphep+h0aCkWj5G9miW
|
|
||||||
r0ypWrjjoGDFOp53AZuJ1sLAdgQYAQgAKgUCaEj9VQkQ3weSNhbTZdcWIQQAzpwP
|
|
||||||
AGodxV8pf5vfB5I2FtNl1wIbDAAAXFYH/0B/uqlgqV0PPmI1hCW4Wg9/IcBWU6mJ
|
|
||||||
qR+G+e7uGKNMSAHV+sHIq7ab6TibuAA0GB9aV2xiLV95YFWfp+yle4JzSTuimZCC
|
|
||||||
iXu55Ouwac0HtTSXXgdwpJMtnZz8m3tlLppdePGlg+rT/mW3z7mxGRfEcj0eEDHq
|
|
||||||
Ar9EkI0hNG39X+BPhhZZvPxUeCQ8h8cWyOvxutWGbhX/4kpkAbh5I1CnhCtOl0iN
|
|
||||||
19rWt33Wp9/KtaE81NASsTaMU5dJT86iN0OMYnunCvSqPgUcAJUvf+ipiiDY2tRZ
|
|
||||||
/3ZBzDBasRk1lSkkQxQHmr606hnjSWXQDyWy8AFQtkOpa95ilFAX6tA=
|
|
||||||
=BAfD
|
|
||||||
-----END PGP PRIVATE KEY BLOCK-----
|
|
||||||
`
|
|
||||||
publicKey = `
|
|
||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
|
||||||
xsBNBGhI/VUBCADDVSm3mKY5JsncMMJFV0zELMrmip7dkK3vvMvVWVmMHiC4akDH
|
|
||||||
WPxUdWNQPjE2e5HGF9Ebg0c7gu634VG470MnTzFdPV6f5zA+yJfdKrLq7gpl9QGW
|
|
||||||
jFLIeK/l4xc+MIpOE1rD9WqYUUw2IYY8YANYq4yB36rq41VuZps/adI9Go5IhfcU
|
|
||||||
3SVb7o7pa/gWE0FVu9ze31j2agC8FIKHgB++7bmYgbAQz5Qi1qgtG0Kn25QUacJ6
|
|
||||||
Akm2+h4w3SQCR6HLRV2BO29x9mFBszf2KQ7DW2VNiGyUuQQ3m8v2ZidG/11ff6U6
|
|
||||||
ad5tvr/8sYr5jOnKEJyDP9v9yQ04cU94GmsPABEBAAHNL1Rlc3RVc2VyMSAoVGVz
|
|
||||||
dFVzZXIxKSA8dGVzdHVzZXJAcm9zc2tlZW4uaG91c2U+wsCKBBMBCAA+BQJoSP1V
|
|
||||||
CRDfB5I2FtNl1xYhBADOnA8Aah3FXyl/m98HkjYW02XXAhsDAh4BAhkBAgsHAhUI
|
|
||||||
AxYAAgMnBwIAAGKLB/9whwgfLkoTYI+Q0Te2uZXl7FPyW0t4tbUSoI1aiVw+ymND
|
|
||||||
V+wuqYsjYDob2MyK/TAFkSCqZhBIRbGLRJtwzQwkF/amGuelHSSBX3LdxK/sp2UU
|
|
||||||
+zv/NBEP1LlUNKchuxpdBPYjKHdbOLPoKqFfdCujSxpWTIeKCwvnDtuP+PlsjUgc
|
|
||||||
afxMx+cVwe77AZ1Fi5CBD9Dr7nNsLovywrSiszyVh5eT0pbcu8Elf5oYQNT1q3ms
|
|
||||||
aqrw95shlGQiwVgrfTvRCDeZnKafHwMUoh48otsNI9aS0HpChi+JNd+PLe/Wlsj5
|
|
||||||
x+q0sHc+5/24zQvN0Wx9fPE4/7z8Pr+CUhAxjI7hzsBNBGhI/VUBCACfahoi2GRx
|
|
||||||
OfOqJ4ZN8Te+4bJeOQdzTofAisfCU736q+MvEKtAuY72vzWdERWpn7XABmtkfII8
|
|
||||||
Xyya8iHKuEGCETy0YoZ15GLBgSHhLnlN2U/4BkrUjGvUjZWquW22gfFB1q8ZdSGw
|
|
||||||
pg8hrspY/b+nnAGdawq+hfE6YRQTmG5tkBrctxfxglBnKzezmL1a3arTHWf2SRlL
|
|
||||||
eAWNI4sACryP/5plD6+9Z9YXTKeDlXnga6BzB80y7F2g0cPdtX20dWAGx4irq2I2
|
|
||||||
SxlABvWMZrdm2Upr6yLHD2zTpbf1FkKFUw9j0lrL1fMq0/j9zbKJsTh6CtOlF04s
|
|
||||||
AeoEV9SZ36GvABEBAAHCwHYEGAEIACoFAmhI/VUJEN8HkjYW02XXFiEEAM6cDwBq
|
|
||||||
HcVfKX+b3weSNhbTZdcCGwwAAFxWB/9Af7qpYKldDz5iNYQluFoPfyHAVlOpiakf
|
|
||||||
hvnu7hijTEgB1frByKu2m+k4m7gANBgfWldsYi1feWBVn6fspXuCc0k7opmQgol7
|
|
||||||
ueTrsGnNB7U0l14HcKSTLZ2c/Jt7ZS6aXXjxpYPq0/5lt8+5sRkXxHI9HhAx6gK/
|
|
||||||
RJCNITRt/V/gT4YWWbz8VHgkPIfHFsjr8brVhm4V/+JKZAG4eSNQp4QrTpdIjdfa
|
|
||||||
1rd91qffyrWhPNTQErE2jFOXSU/OojdDjGJ7pwr0qj4FHACVL3/oqYog2NrUWf92
|
|
||||||
QcwwWrEZNZUpJEMUB5q+tOoZ40ll0A8lsvABULZDqWveYpRQF+rQ
|
|
||||||
=jBvZ
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewOpenPGPKeyRingConfig(t *testing.T) {
|
|
||||||
p := NewOpenPGPKeyRing()
|
|
||||||
assert.NotNil(t, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewOpenPGPKeyRingConfigYAML(t *testing.T) {
|
|
||||||
p := NewOpenPGPKeyRing()
|
|
||||||
assert.NotNil(t, p)
|
|
||||||
|
|
||||||
config := fmt.Sprintf(`
|
|
||||||
keyring: |-
|
|
||||||
%s
|
|
||||||
%s
|
|
||||||
`, publicKey, privateKey)
|
|
||||||
|
|
||||||
yamlErr := p.LoadYAML(config)
|
|
||||||
assert.Nil(t, yamlErr)
|
|
||||||
|
|
||||||
/*
|
|
||||||
crt, err := p.GetValue("catemplate")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, []string{"RKH"}, crt.(*x509.Certificate).Subject.Organization)
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
|
@ -1,135 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
|
||||||
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"strconv"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"decl/internal/data"
|
|
||||||
"decl/internal/codec"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OpenPGPSignature struct {
|
|
||||||
KeyRing string `json:"keyring,omitempty" yaml:"keyring,omitempty"`
|
|
||||||
Signature string `json:"signature,omitempty" yaml:"signature,omitempty"`
|
|
||||||
entities openpgp.EntityList
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOpenPGPSignature() *OpenPGPSignature {
|
|
||||||
o := &OpenPGPSignature{}
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) URI() string {
|
|
||||||
return fmt.Sprintf("%s://%s", o.Type(), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) SetURI(uri string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) SetParsedURI(uri data.URIParser) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) Read(ctx context.Context) (yamlData []byte, err error) {
|
|
||||||
pemReader := io.NopCloser(strings.NewReader(o.KeyRing))
|
|
||||||
o.entities, err = openpgp.ReadArmoredKeyRing(pemReader)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) Load(r io.Reader) (err error) {
|
|
||||||
err = codec.NewYAMLDecoder(r).Decode(o)
|
|
||||||
if err == nil {
|
|
||||||
_, err = o.Read(context.Background())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) LoadYAML(yamlData string) (err error) {
|
|
||||||
err = codec.NewYAMLStringDecoder(yamlData).Decode(o)
|
|
||||||
slog.Info("OpenPGP.LoadYAML()", "keyring", len(o.KeyRing), "err", err)
|
|
||||||
if err == nil {
|
|
||||||
_, err = o.Read(context.Background())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) UnmarshalJSON(data []byte) error {
|
|
||||||
if unmarshalErr := json.Unmarshal(data, o); unmarshalErr != nil {
|
|
||||||
return unmarshalErr
|
|
||||||
}
|
|
||||||
//o.NewReadConfigCommand()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) UnmarshalYAML(value *yaml.Node) error {
|
|
||||||
type decodeOpenPGP OpenPGPSignature
|
|
||||||
if unmarshalErr := value.Decode((*decodeOpenPGP)(o)); unmarshalErr != nil {
|
|
||||||
return unmarshalErr
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) Clone() data.Configuration {
|
|
||||||
jsonGeneric, _ := json.Marshal(o)
|
|
||||||
clone := NewOpenPGPSignature()
|
|
||||||
if unmarshalErr := json.Unmarshal(jsonGeneric, &clone); unmarshalErr != nil {
|
|
||||||
panic(unmarshalErr)
|
|
||||||
}
|
|
||||||
return clone
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) Type() string {
|
|
||||||
return "openpgp"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) GetEntityIndex(key string) (index int, field string, err error) {
|
|
||||||
values := strings.SplitN(key, ".", 2)
|
|
||||||
if len(values) == 2 {
|
|
||||||
if index, err = strconv.Atoi(values[0]); err == nil {
|
|
||||||
field = values[1]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = data.ErrUnknownConfigurationKey
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) GetValue(name string) (result any, err error) {
|
|
||||||
index, field, err := o.GetEntityIndex(name)
|
|
||||||
if len(o.entities) > index && err == nil {
|
|
||||||
switch field {
|
|
||||||
case "PublicKey":
|
|
||||||
return o.entities[index].PrimaryKey, err
|
|
||||||
case "PrivateKey":
|
|
||||||
return o.entities[index].PrimaryKey, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = data.ErrUnknownConfigurationKey
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Expected key: 0.PrivateKey
|
|
||||||
func (o *OpenPGPSignature) Has(key string) (ok bool) {
|
|
||||||
index, field, err := o.GetEntityIndex(key)
|
|
||||||
if len(o.entities) > index && err == nil {
|
|
||||||
switch field {
|
|
||||||
case "PublicKey":
|
|
||||||
ok = o.entities[index].PrimaryKey != nil
|
|
||||||
case "PrivateKey":
|
|
||||||
ok = o.entities[index].PrimaryKey != nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewOpenPGPSignatureConfig(t *testing.T) {
|
|
||||||
p := NewOpenPGPSignature()
|
|
||||||
assert.NotNil(t, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewOpenPGPSignatureConfigYAML(t *testing.T) {
|
|
||||||
p := NewOpenPGPSignature()
|
|
||||||
assert.NotNil(t, p)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
config := fmt.Sprintf(`
|
|
||||||
keyring: |-
|
|
||||||
%s
|
|
||||||
%s
|
|
||||||
`, publicKey, privateKey)
|
|
||||||
|
|
||||||
yamlErr := p.LoadYAML(config)
|
|
||||||
assert.Nil(t, yamlErr)
|
|
||||||
|
|
||||||
p.Read(context.Background())
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"$id": "openpgp.schema.json",
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "openpgp",
|
|
||||||
"type": "object",
|
|
||||||
"required": [ "path", "filetype" ],
|
|
||||||
"properties": {
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,7 +10,6 @@ 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
|
||||||
@ -36,25 +35,9 @@ 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",
|
|
||||||
}
|
|
||||||
s["keyringpath"] = "/etc/jx/pgp/keyring.asc"
|
|
||||||
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(), "")
|
||||||
}
|
}
|
||||||
@ -63,7 +46,7 @@ func (s *System) SetURI(uri string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *System) SetParsedURI(uri data.URIParser) error {
|
func (s *System) SetParsedURI(uri *url.URL) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package containerlog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Header(r io.Reader) (s StreamType, size uint64, err error) {
|
|
||||||
var header []byte = make([]byte, 8)
|
|
||||||
if _, err = io.ReadFull(r, header); err == nil {
|
|
||||||
s = StreamType(header[0])
|
|
||||||
if err = s.Validate(); err == nil {
|
|
||||||
header[0] = 0x0
|
|
||||||
size = binary.BigEndian.Uint64(header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadMessage(r io.Reader, size uint64) (message string, err error) {
|
|
||||||
var messageData []byte = make([]byte, size)
|
|
||||||
var bytesRead int
|
|
||||||
if bytesRead, err = r.Read(messageData); err == nil && uint64(bytesRead) == size {
|
|
||||||
message = string(messageData)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func Read(r io.Reader) (s StreamType, message string, err error) {
|
|
||||||
var messageSize uint64
|
|
||||||
if s, messageSize, err = Header(r); err == nil {
|
|
||||||
if message, err = ReadMessage(r, messageSize); err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package containerlog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
"bytes"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLogHeader(t *testing.T) {
|
|
||||||
for _, v := range []struct{ expected error; value []byte } {
|
|
||||||
{ expected: nil, value: StreamStdout.Log("test message") },
|
|
||||||
{ expected: nil, value: StreamStderr.Log("test error") },
|
|
||||||
{ expected: ErrInvalidStreamType, value: StreamType(0x3).Log("fail") },
|
|
||||||
{ expected: ErrInvalidStreamType, value: StreamType(0x4).Log("fail") },
|
|
||||||
} {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_, e := buf.Write(v.value)
|
|
||||||
assert.Nil(t, e)
|
|
||||||
logType, logSize, err := Header(&buf)
|
|
||||||
assert.ErrorIs(t, err, v.expected)
|
|
||||||
assert.ErrorIs(t, logType.Validate(), v.expected)
|
|
||||||
if err == nil {
|
|
||||||
assert.Equal(t, uint64(len(v.value) - 8), logSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package containerlog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StreamReader struct {
|
|
||||||
source io.Reader
|
|
||||||
streams []*ReadBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReadBuffer struct {
|
|
||||||
streamtype StreamType
|
|
||||||
reader *StreamReader
|
|
||||||
bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStreamReader(source io.Reader) (s *StreamReader) {
|
|
||||||
s = &StreamReader{
|
|
||||||
source: source,
|
|
||||||
streams: make([]*ReadBuffer, 3),
|
|
||||||
}
|
|
||||||
s.streams[StreamStdout] = &ReadBuffer{
|
|
||||||
streamtype: StreamStdout,
|
|
||||||
reader: s,
|
|
||||||
}
|
|
||||||
s.streams[StreamStderr] = &ReadBuffer{
|
|
||||||
streamtype: StreamStderr,
|
|
||||||
reader: s,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StreamReader) StdoutPipe() io.ReadCloser {
|
|
||||||
return s.streams[StreamStdout]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StreamReader) StderrPipe() io.ReadCloser {
|
|
||||||
return s.streams[StreamStderr]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *ReadBuffer) Read(p []byte) (n int, e error) {
|
|
||||||
for {
|
|
||||||
if b.reader.streams[b.streamtype].Len() >= len(p) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
streamtype, message, err := Read(b.reader.source)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.reader.streams[streamtype] == nil {
|
|
||||||
b.reader.streams[streamtype] = &ReadBuffer{
|
|
||||||
streamtype: streamtype,
|
|
||||||
reader: b.reader,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if bytesRead, bufferErr := b.reader.streams[streamtype].WriteString(message); bytesRead != len(message) || bufferErr != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.Buffer.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *ReadBuffer) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package containerlog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStreamReader(t *testing.T) {
|
|
||||||
|
|
||||||
var logs bytes.Buffer
|
|
||||||
logs.Write(StreamStdout.Log("stdout log message"))
|
|
||||||
logs.Write(StreamStderr.Log("stderr log message"))
|
|
||||||
logs.Write(StreamStdout.Log("stdout log message - line 2"))
|
|
||||||
logs.Write(StreamStderr.Log("stderr log message - line 2"))
|
|
||||||
logs.Write(StreamStderr.Log("stderr log message - line 3"))
|
|
||||||
|
|
||||||
sr := NewStreamReader(&logs)
|
|
||||||
outpipe := sr.StdoutPipe()
|
|
||||||
errpipe := sr.StderrPipe()
|
|
||||||
|
|
||||||
var message []byte = make([]byte, 20)
|
|
||||||
n, ee := errpipe.Read(message)
|
|
||||||
assert.Nil(t, ee)
|
|
||||||
assert.Equal(t, 20, n)
|
|
||||||
|
|
||||||
assert.Equal(t, "stderr log messagest", string(message))
|
|
||||||
|
|
||||||
ov, oe := io.ReadAll(outpipe)
|
|
||||||
assert.Nil(t, oe)
|
|
||||||
assert.Equal(t, "stdout log messagestdout log message - line 2", string(ov))
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package containerlog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"errors"
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StreamType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
StreamStdin StreamType = 0x0
|
|
||||||
StreamStdout StreamType = 0x1
|
|
||||||
StreamStderr StreamType = 0x2
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidStreamType error = errors.New("Invalid container log stream type")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s StreamType) Validate() error {
|
|
||||||
switch s {
|
|
||||||
case StreamStdin, StreamStdout, StreamStderr:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w: %d", ErrInvalidStreamType, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s StreamType) Log(msg string) (log []byte) {
|
|
||||||
msgLen := len(msg)
|
|
||||||
log = make([]byte, 8 + msgLen)
|
|
||||||
binary.BigEndian.PutUint64(log, uint64(msgLen))
|
|
||||||
log[0] = byte(s)
|
|
||||||
copy(log[8:], []byte(msg))
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package containerlog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStreamType(t *testing.T) {
|
|
||||||
for _, v := range []struct{ expected error; value StreamType } {
|
|
||||||
{ expected: nil, value: 0x0 },
|
|
||||||
{ expected: nil, value: 0x1 },
|
|
||||||
{ expected: nil, value: 0x2 },
|
|
||||||
{ expected: ErrInvalidStreamType, value: 0x3 },
|
|
||||||
{ expected: ErrInvalidStreamType, value: 0x4 },
|
|
||||||
} {
|
|
||||||
assert.ErrorIs(t, v.value.Validate(), v.expected)
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
ErrConfigUndefinedName = errors.New("Config block is missing a defined name")
|
ErrConfigUndefinedName = errors.New("Config block is missing a defined name")
|
||||||
ErrConfigUndefined = errors.New("Config block is missing")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package data
|
|
||||||
|
|
||||||
import (
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommandExecutor interface {
|
|
||||||
Execute(value any) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandOutputExtractor interface {
|
|
||||||
Extract(output []byte, target any) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandChecker interface {
|
|
||||||
Exists() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Commander interface {
|
|
||||||
CommandExecutor
|
|
||||||
CommandOutputExtractor
|
|
||||||
CommandChecker
|
|
||||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"io"
|
"io"
|
||||||
"decl/internal/mapper"
|
"decl/internal/mapper"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -43,7 +44,7 @@ type Document interface {
|
|||||||
mapper.Mapper
|
mapper.Mapper
|
||||||
|
|
||||||
NewResource(uri string) (Resource, error)
|
NewResource(uri string) (Resource, error)
|
||||||
NewResourceFromParsedURI(uri URIParser) (Resource, error)
|
NewResourceFromParsedURI(uri *url.URL) (Resource, error)
|
||||||
AddDeclaration(Declaration)
|
AddDeclaration(Declaration)
|
||||||
AddResourceDeclaration(resourceType string, resourceDeclaration Resource)
|
AddResourceDeclaration(resourceType string, resourceDeclaration Resource)
|
||||||
|
|
||||||
|
@ -5,34 +5,16 @@ 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
|
||||||
SetParsedURI(URIParser) error
|
SetURI(string) error
|
||||||
|
SetParsedURI(*url.URL) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type DocumentElement interface {
|
type DocumentElement interface {
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package data
|
|
||||||
|
|
||||||
import (
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
)
|
|
||||||
|
|
||||||
type Originator interface {
|
|
||||||
GetContentReadWriter() ContentReadWriter
|
|
||||||
}
|
|
@ -22,11 +22,6 @@ 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
|
||||||
@ -38,6 +33,7 @@ type Resource interface {
|
|||||||
Crudder
|
Crudder
|
||||||
Validator
|
Validator
|
||||||
Clone() Resource
|
Clone() Resource
|
||||||
|
SetResourceMapper(ResourceMapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Declaration interface {
|
type Declaration interface {
|
||||||
@ -105,13 +101,6 @@ type FileResource interface {
|
|||||||
SetGzipContent(bool)
|
SetGzipContent(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExecResource interface {
|
|
||||||
Start() error
|
|
||||||
Wait() error
|
|
||||||
StdoutPipe() (io.ReadCloser, error)
|
|
||||||
StderrPipe() (io.ReadCloser, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Signed interface {
|
type Signed interface {
|
||||||
Signature() Signature
|
Signature() Signature
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ 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]
|
||||||
}
|
}
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
// 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"))
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
// 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,6 +105,12 @@ 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 {
|
||||||
@ -124,12 +130,8 @@ 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,10 +120,8 @@ 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" }
|
||||||
@ -149,16 +147,12 @@ func (j *JxFile) Extract(resourceSource data.Resource, filter data.ElementSelect
|
|||||||
}
|
}
|
||||||
|
|
||||||
uri := resourceSource.URI()
|
uri := resourceSource.URI()
|
||||||
documentIndexUri := fmt.Sprintf("%s?index=%d", uri, j.index)
|
if folio.DocumentRegistry.HasDocument(folio.URI(uri)) {
|
||||||
|
uri = fmt.Sprintf("%s?index=%d", uri, j.index)
|
||||||
doc = folio.DocumentRegistry.NewDocument(folio.URI(documentIndexUri))
|
|
||||||
if ! folio.DocumentRegistry.HasDocument(folio.URI(uri)) {
|
|
||||||
folio.DocumentRegistry.SetDocument(folio.URI(uri), doc.(*folio.Document))
|
|
||||||
doc.(*folio.Document).SetURI(uri)
|
|
||||||
}
|
}
|
||||||
|
doc = folio.DocumentRegistry.NewDocument(folio.URI(uri))
|
||||||
err = j.decoder.Decode(doc)
|
err = j.decoder.Decode(doc)
|
||||||
slog.Info("JxFile.Extract()", "uri", uri, "doc", doc, "jxfile", j, "error", err)
|
slog.Info("JxFile.Extract()", "doc", doc, "jxfile", j, "error", err)
|
||||||
j.index++
|
j.index++
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -185,7 +179,6 @@ 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
|
||||||
@ -193,7 +186,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().URL()
|
targetUrl := j.Uri.Parse()
|
||||||
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,32 +15,6 @@ 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"`
|
||||||
@ -161,9 +135,9 @@ func (b *Block) SetURI(uri string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Block) SetParsedURI(uri data.URIParser) (err error) {
|
func (b *Block) SetParsedURI(uri *url.URL) (err error) {
|
||||||
if b.Values == nil {
|
if b.Values == nil {
|
||||||
if err = b.NewConfigurationFromParsedURI(uri.URL()); err != nil {
|
if err = b.NewConfigurationFromParsedURI(uri); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,12 @@ _ "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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,7 +33,6 @@ 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 {
|
||||||
@ -42,15 +43,14 @@ 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"`
|
||||||
On *Events `json:"on,omitempty" yaml:"on,omitempty"`
|
// runtime luaruntime.LuaRunner
|
||||||
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, runtime: luaruntime.New() }
|
return &Declaration{ ResourceTypes: DocumentRegistry.ResourceTypes }
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeclarationFromDocument(document *Document) *Declaration {
|
func NewDeclarationFromDocument(document *Document) *Declaration {
|
||||||
@ -73,7 +73,7 @@ func (d *Declaration) SetDocument(newDocument *Document) {
|
|||||||
d.document = newDocument
|
d.document = newDocument
|
||||||
d.SetConfig(d.document.config)
|
d.SetConfig(d.document.config)
|
||||||
d.ResourceTypes = d.document.Types()
|
d.ResourceTypes = d.document.Types()
|
||||||
d.Attributes.(ResourceMapSetter).SetResourceMapper(d.document.uris)
|
d.Attributes.SetResourceMapper(d.document.uris)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) ResolveId(ctx context.Context) string {
|
func (d *Declaration) ResolveId(ctx context.Context) string {
|
||||||
@ -98,7 +98,6 @@ 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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,12 +137,11 @@ func (d *Declaration) Validate() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) NewResourceFromParsedURI(u data.URIParser) (err error) {
|
func (d *Declaration) NewResourceFromParsedURI(u *url.URL) (err error) {
|
||||||
if u == nil {
|
if u == nil {
|
||||||
d.Attributes, err = d.ResourceTypes.NewFromType(string(d.Type))
|
d.Attributes, err = d.ResourceTypes.NewFromParsedURI(&url.URL{ Scheme: string(d.Type) })
|
||||||
} else {
|
} else {
|
||||||
parsed := u.URL()
|
if d.Attributes, err = d.ResourceTypes.NewFromParsedURI(u); err == nil {
|
||||||
if d.Attributes, err = d.ResourceTypes.NewFromParsedURI(parsed); err == nil {
|
|
||||||
err = d.Attributes.SetParsedURI(u)
|
err = d.Attributes.SetParsedURI(u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,17 +149,15 @@ func (d *Declaration) NewResourceFromParsedURI(u data.URIParser) (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.NewFromType(string(d.Type))
|
d.Attributes, err = d.ResourceTypes.New(fmt.Sprintf("%s://", d.Type))
|
||||||
} else {
|
} else {
|
||||||
slog.Info("Declaration.NewResource()", "uri", *uri)
|
if d.Attributes, err = d.ResourceTypes.New(*uri); err == nil {
|
||||||
parsedURI := URI(*uri).Parse()
|
err = d.Attributes.SetURI(*uri)
|
||||||
d.Attributes, err = d.ResourceTypes.NewFromParsedURI(parsedURI.URL())
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -173,6 +169,7 @@ 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)
|
||||||
@ -209,11 +206,6 @@ func (d *Declaration) Apply(stateTransition string) (result error) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
result = stater.Trigger("read")
|
result = stater.Trigger("read")
|
||||||
case "restart": // XXX should only work for a process type resource
|
|
||||||
if result = stater.Trigger("restart"); result != nil {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
result = stater.Trigger("read")
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: %s on %s", ErrUnknownStateTransition, stateTransition, d.Attributes.URI())
|
return fmt.Errorf("%w: %s on %s", ErrUnknownStateTransition, stateTransition, d.Attributes.URI())
|
||||||
case "create", "present":
|
case "create", "present":
|
||||||
@ -224,7 +216,6 @@ func (d *Declaration) Apply(stateTransition string) (result error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Declaration.Apply() - read", "state", stater.CurrentState(), "declaration", d)
|
|
||||||
result = stater.Trigger("read")
|
result = stater.Trigger("read")
|
||||||
currentState := stater.CurrentState()
|
currentState := stater.CurrentState()
|
||||||
switch currentState {
|
switch currentState {
|
||||||
@ -254,7 +245,7 @@ func (d *Declaration) SetConfig(configDoc data.Document) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if d.Config != "" { // XXX
|
if d.Config != "" { // XXX
|
||||||
panic(fmt.Errorf("%w: failed setting config: %s", data.ErrConfigUndefined, d.Config))
|
panic("failed setting config")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,7 +254,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.SetParsedURI(URI(uri).Parse())
|
err = d.Attributes.SetURI(uri)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -277,7 +268,7 @@ func (d *Declaration) SetURI(uri string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) SetParsedURI(uri data.URIParser) (err error) {
|
func (d *Declaration) SetParsedURI(uri *url.URL) (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)
|
||||||
@ -307,9 +298,7 @@ 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
|
||||||
d.On = value.On
|
newResource, resourceErr := d.ResourceTypes.New(fmt.Sprintf("%s://", value.Type))
|
||||||
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)
|
||||||
@ -317,30 +306,10 @@ 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) (err error) {
|
func (d *Declaration) UnmarshalYAML(value *yaml.Node) error {
|
||||||
if d.ResourceTypes == nil {
|
if d.ResourceTypes == nil {
|
||||||
d.ResourceTypes = DocumentRegistry.ResourceTypes
|
d.ResourceTypes = DocumentRegistry.ResourceTypes
|
||||||
}
|
}
|
||||||
@ -362,17 +331,10 @@ func (d *Declaration) UnmarshalYAML(value *yaml.Node) (err 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) (err error) {
|
func (d *Declaration) UnmarshalJSON(jsonData []byte) error {
|
||||||
if d.ResourceTypes == nil {
|
if d.ResourceTypes == nil {
|
||||||
d.ResourceTypes = DocumentRegistry.ResourceTypes
|
d.ResourceTypes = DocumentRegistry.ResourceTypes
|
||||||
}
|
}
|
||||||
@ -392,18 +354,24 @@ func (d *Declaration) UnmarshalJSON(jsonData []byte) (err error) {
|
|||||||
return unmarshalAttributesErr
|
return unmarshalAttributesErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if i, ok := d.Attributes.(data.ResourceInitializer); ok {
|
return nil
|
||||||
err = i.Init(nil)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("failed to execute init")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) GetContentReadWriter() data.ContentReadWriter {
|
/*
|
||||||
return d.Resource().(data.ContentReadWriter)
|
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,7 +57,6 @@ 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,6 +10,7 @@ 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"
|
||||||
@ -18,7 +19,6 @@ _ "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 {
|
||||||
@ -37,30 +37,20 @@ type Document struct {
|
|||||||
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
||||||
Imports []URI `json:"imports,omitempty" yaml:"imports,omitempty"`
|
Imports []URI `json:"imports,omitempty" yaml:"imports,omitempty"`
|
||||||
Errors []string `json:"error,omitempty" yaml:"error,omitempty"`
|
Errors []string `json:"error,omitempty" yaml:"error,omitempty"`
|
||||||
uris mapper.Store[URI, *Declaration]
|
uris mapper.Store[string, data.Declaration]
|
||||||
ResourceDeclarations []*Declaration `json:"resources,omitempty" yaml:"resources,omitempty"`
|
ResourceDeclarations []*Declaration `json:"resources,omitempty" yaml:"resources,omitempty"`
|
||||||
configNames mapper.Store[string, data.Block] `json:"-" yaml:"-"`
|
configNames mapper.Store[string, data.Block] `json:"-" yaml:"-"`
|
||||||
Configurations []*Block `json:"configurations,omitempty" yaml:"configurations,omitempty"`
|
Configurations []*Block `json:"configurations,omitempty" yaml:"configurations,omitempty"`
|
||||||
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[URI, *Declaration](),
|
|
||||||
configNames: mapper.New[string, data.Block](),
|
|
||||||
importPaths: NewSearchPath(configImportPath.GetStringSlice()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) GetURI() string {
|
func (d *Document) GetURI() string {
|
||||||
@ -69,19 +59,6 @@ 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] {
|
||||||
@ -111,24 +88,20 @@ func (d *Document) Filter(filter data.DeclarationSelector) []data.Declaration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Has(key string) bool {
|
func (d *Document) Has(key string) bool {
|
||||||
return d.uris.Has(URI(key))
|
return d.uris.Has(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Get(key string) (any, bool) {
|
func (d *Document) Get(key string) (any, bool) {
|
||||||
return d.uris.Get(URI(key))
|
return d.uris.Get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Set(key string, value any) {
|
func (d *Document) Set(key string, value any) {
|
||||||
d.uris.Set(URI(key), value.(*Declaration))
|
d.uris.Set(key, value.(data.Declaration))
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) Delete(key string) {
|
|
||||||
d.uris.Delete(URI(key))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) GetResource(uri string) *Declaration {
|
func (d *Document) GetResource(uri string) *Declaration {
|
||||||
if decl, ok := d.uris[URI(uri)]; ok {
|
if decl, ok := d.uris[uri]; ok {
|
||||||
return decl
|
return decl.(*Declaration)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -169,15 +142,7 @@ 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) {
|
||||||
var load URI = uri
|
if _, err = DocumentRegistry.Load(uri); err != nil {
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -236,17 +201,15 @@ func (d *Document) GetSchemaFiles() (schemaFs fs.FS) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
schemaFs, _ = d.Registry.Schemas.Get(d.Registry.DefaultSchema)
|
schemaFs, _ = d.Registry.Schemas.Get(d.Registry.DefaultSchema)
|
||||||
slog.Info("Document.GetSchemaFiles() default schema", "schema", d.Registry.DefaultSchema, "schemaFs", schemaFs)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Validate() error {
|
func (d *Document) Validate() error {
|
||||||
jsonDocument, jsonErr := d.JSON()
|
jsonDocument, jsonErr := d.JSON()
|
||||||
slog.Info("Document.Validate() convert to json", "err", jsonErr)
|
slog.Info("document.Validate() json", "err", jsonErr)
|
||||||
if jsonErr == nil {
|
if jsonErr == nil {
|
||||||
s := schema.New("document", d.GetSchemaFiles())
|
s := schema.New("document", d.GetSchemaFiles())
|
||||||
err := s.Validate(string(jsonDocument))
|
err := s.Validate(string(jsonDocument))
|
||||||
slog.Info("Document.Validate() validate schema", "error", err)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -356,12 +319,8 @@ func (d *Document) MapConfigurationURI(uri string, block data.Block) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (d *Document) MapResourceURI(uri string, declaration *Declaration) {
|
func (d *Document) MapResourceURI(uri string, declaration data.Declaration) {
|
||||||
d.uris[URI(uri)] = declaration
|
d.uris[uri] = declaration
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) UnMapResourceURI(uri string) {
|
|
||||||
d.uris.Delete(URI(uri))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) AddDeclaration(declaration data.Declaration) {
|
func (d *Document) AddDeclaration(declaration data.Declaration) {
|
||||||
@ -371,7 +330,7 @@ func (d *Document) AddDeclaration(declaration data.Declaration) {
|
|||||||
|
|
||||||
d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
||||||
|
|
||||||
d.MapResourceURI(uri, decl)
|
d.MapResourceURI(uri, declaration)
|
||||||
decl.SetDocument(d)
|
decl.SetDocument(d)
|
||||||
d.Registry.DeclarationMap[decl] = d
|
d.Registry.DeclarationMap[decl] = d
|
||||||
}
|
}
|
||||||
@ -423,7 +382,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 data.URIParser) (newResource data.Resource, err error) {
|
func (d *Document) NewResourceFromParsedURI(uri *url.URL) (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)
|
||||||
}
|
}
|
||||||
@ -537,35 +496,6 @@ func (d *Document) DiffState(output io.Writer) (returnOutput string, diffErr err
|
|||||||
return d.Diff(clone, output)
|
return d.Diff(clone, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) YamlDiff(with data.Document) (diffs []*yamldiff.YamlDiff, diffErr error) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
diffErr = fmt.Errorf("%s", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
opts := []yamldiff.DoOptionFunc{}
|
|
||||||
ydata, yerr := d.YAML()
|
|
||||||
if yerr != nil {
|
|
||||||
return nil, yerr
|
|
||||||
}
|
|
||||||
yamlDiff,yamlDiffErr := yamldiff.Load(string(ydata))
|
|
||||||
if yamlDiffErr != nil {
|
|
||||||
return nil, yamlDiffErr
|
|
||||||
}
|
|
||||||
wdata,werr := with.YAML()
|
|
||||||
if werr != nil {
|
|
||||||
return nil, werr
|
|
||||||
}
|
|
||||||
withDiff,withDiffErr := yamldiff.Load(string(wdata))
|
|
||||||
if withDiffErr != nil {
|
|
||||||
return nil, withDiffErr
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("Document.Diff() ", "document.yaml", ydata, "with.yaml", wdata)
|
|
||||||
return yamldiff.Do(yamlDiff, withDiff, opts...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) Diff(with data.Document, output io.Writer) (returnOutput string, diffErr error) {
|
func (d *Document) Diff(with data.Document, output io.Writer) (returnOutput string, diffErr error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -573,21 +503,37 @@ func (d *Document) Diff(with data.Document, output io.Writer) (returnOutput stri
|
|||||||
diffErr = fmt.Errorf("%s", r)
|
diffErr = fmt.Errorf("%s", r)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
slog.Info("Document.Diff()")
|
||||||
|
opts := []yamldiff.DoOptionFunc{}
|
||||||
if output == nil {
|
if output == nil {
|
||||||
output = &strings.Builder{}
|
output = &strings.Builder{}
|
||||||
}
|
}
|
||||||
|
ydata, yerr := d.YAML()
|
||||||
|
if yerr != nil {
|
||||||
|
return "", yerr
|
||||||
|
}
|
||||||
|
yamlDiff,yamlDiffErr := yamldiff.Load(string(ydata))
|
||||||
|
if yamlDiffErr != nil {
|
||||||
|
return "", yamlDiffErr
|
||||||
|
}
|
||||||
|
|
||||||
var diffs []*yamldiff.YamlDiff
|
wdata,werr := with.YAML()
|
||||||
diffs, diffErr = d.YamlDiff(with)
|
if werr != nil {
|
||||||
|
return "", werr
|
||||||
|
}
|
||||||
|
withDiff,withDiffErr := yamldiff.Load(string(wdata))
|
||||||
|
if withDiffErr != nil {
|
||||||
|
return "", withDiffErr
|
||||||
|
}
|
||||||
|
|
||||||
for _,docDiffResults := range diffs {
|
for _,docDiffResults := range yamldiff.Do(yamlDiff, withDiff, opts...) {
|
||||||
slog.Info("Diff()", "diff", docDiffResults, "dump", docDiffResults.Dump())
|
slog.Info("Diff()", "diff", docDiffResults, "dump", docDiffResults.Dump())
|
||||||
_,e := output.Write([]byte(docDiffResults.Dump()))
|
_,e := output.Write([]byte(docDiffResults.Dump()))
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return "", e
|
return "", e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
slog.Info("Document.Diff() ", "document.yaml", ydata, "with.yaml", wdata)
|
||||||
if stringOutput, ok := output.(*strings.Builder); ok {
|
if stringOutput, ok := output.(*strings.Builder); ok {
|
||||||
return stringOutput.String(), nil
|
return stringOutput.String(), nil
|
||||||
}
|
}
|
||||||
@ -597,80 +543,35 @@ func (d *Document) Diff(with data.Document, output io.Writer) (returnOutput stri
|
|||||||
/*
|
/*
|
||||||
func (d *Document) UnmarshalValue(value *DocumentType) error {
|
func (d *Document) UnmarshalValue(value *DocumentType) error {
|
||||||
d.Requires = value.Requires
|
d.Requires = value.Requires
|
||||||
|
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (d *Document) UnmarshalLoadDependencies() (err error) {
|
func (d *Document) UnmarshalYAML(value *yaml.Node) error {
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("%s - %w", r, err)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("%s", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
err = d.loadImports()
|
|
||||||
d.assignConfigurationsDocument()
|
|
||||||
d.assignResourcesDocument()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) UnmarshalYAML(value *yaml.Node) (err error) {
|
|
||||||
type decodeDocument Document
|
type decodeDocument Document
|
||||||
/*
|
|
||||||
t := &DocumentType{}
|
t := &DocumentType{}
|
||||||
if unmarshalDocumentErr := value.Decode(t); unmarshalDocumentErr != nil {
|
if unmarshalDocumentErr := value.Decode(t); unmarshalDocumentErr != nil {
|
||||||
return unmarshalDocumentErr
|
return unmarshalDocumentErr
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
if unmarshalResourcesErr := value.Decode((*decodeDocument)(d)); unmarshalResourcesErr != nil {
|
if unmarshalResourcesErr := value.Decode((*decodeDocument)(d)); unmarshalResourcesErr != nil {
|
||||||
return unmarshalResourcesErr
|
return unmarshalResourcesErr
|
||||||
}
|
}
|
||||||
err = d.UnmarshalLoadDependencies()
|
d.assignConfigurationsDocument()
|
||||||
return
|
d.assignResourcesDocument()
|
||||||
|
return d.loadImports()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) UnmarshalJSON(data []byte) (err error) {
|
func (d *Document) UnmarshalJSON(data []byte) error {
|
||||||
type decodeDocument Document
|
type decodeDocument Document
|
||||||
t := (*decodeDocument)(d)
|
t := (*decodeDocument)(d)
|
||||||
if unmarshalDocumentErr := json.Unmarshal(data, t); unmarshalDocumentErr != nil {
|
if unmarshalDocumentErr := json.Unmarshal(data, t); unmarshalDocumentErr != nil {
|
||||||
return unmarshalDocumentErr
|
return unmarshalDocumentErr
|
||||||
}
|
}
|
||||||
err = d.UnmarshalLoadDependencies()
|
d.assignConfigurationsDocument()
|
||||||
return
|
d.assignResourcesDocument()
|
||||||
|
return d.loadImports()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) AddError(e error) {
|
func (d *Document) AddError(e error) {
|
||||||
d.Errors = append(d.Errors, e.Error())
|
d.Errors = append(d.Errors, e.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) GetContentReadWriter() data.ContentReadWriter {
|
|
||||||
return d.URI
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) GetContent(w io.Writer) (contentReader io.ReadCloser, err error) {
|
|
||||||
slog.Info("Document.GetContent()")
|
|
||||||
|
|
||||||
var buf strings.Builder
|
|
||||||
err = codec.FormatYaml.Serialize(d, &buf)
|
|
||||||
|
|
||||||
contentReader = io.NopCloser(strings.NewReader(buf.String()))
|
|
||||||
|
|
||||||
if w != nil {
|
|
||||||
copyBuffer := make([]byte, 32 * 1024)
|
|
||||||
_, writeErr := io.CopyBuffer(w, contentReader, copyBuffer)
|
|
||||||
if writeErr != nil {
|
|
||||||
return nil, fmt.Errorf("File.GetContent(): CopyBuffer failed %v %v: %w", w, contentReader, writeErr)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) SetContent(r io.Reader) error {
|
|
||||||
return d.LoadReader(io.NopCloser(r), codec.FormatYaml)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -84,9 +84,6 @@ 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)
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
|||||||
// 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')
|
|
||||||
`)))
|
|
||||||
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
// 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,13 +3,14 @@
|
|||||||
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 {
|
||||||
@ -37,7 +38,7 @@ func (m *MockConfiguration) SetURI(uri string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockConfiguration) SetParsedURI(uri data.URIParser) error {
|
func (m *MockConfiguration) SetParsedURI(uri *url.URL) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ _ "gopkg.in/yaml.v3"
|
|||||||
"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/transport"
|
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -18,30 +17,22 @@ _ "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()
|
||||||
if u != nil {
|
f.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
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()
|
||||||
if u != nil {
|
f.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
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()
|
||||||
if u != nil {
|
f.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
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()
|
||||||
if u != nil {
|
f.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
f.Name = filepath.Join(u.Hostname(), u.Path)
|
|
||||||
}
|
|
||||||
return f
|
return f
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -49,20 +40,23 @@ func RegisterMocks() {
|
|||||||
|
|
||||||
type MockFoo struct {
|
type MockFoo struct {
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
*MockResource `json:",inline" yaml:",inline"`
|
*MockResource `json:"-" yaml:"-"`
|
||||||
|
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:",inline" yaml:",inline"`
|
*MockResource `json:"-" yaml:"-"`
|
||||||
|
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:",inline" yaml:",inline"`
|
*MockResource `json:"-" yaml:"-"`
|
||||||
|
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"`
|
||||||
@ -70,12 +64,13 @@ type MockTestuser struct {
|
|||||||
|
|
||||||
type MockFile struct {
|
type MockFile struct {
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
*MockResource `json:",inline" yaml:",inline"`
|
*MockResource `json:"-" yaml:"-"`
|
||||||
|
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) (m *MockResource) {
|
func NewMockResource(typename string, stater machine.Stater) *MockResource {
|
||||||
m = &MockResource {
|
return &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 },
|
||||||
@ -90,18 +85,10 @@ func NewMockResource(typename string, stater machine.Stater) (m *MockResource) {
|
|||||||
InjectUpdate: func(context.Context) error { return nil },
|
InjectUpdate: func(context.Context) error { return nil },
|
||||||
InjectDelete: func(context.Context) error { return nil },
|
InjectDelete: func(context.Context) error { return nil },
|
||||||
InjectUseConfig: func(data.ConfigurationValueGetter) {},
|
InjectUseConfig: func(data.ConfigurationValueGetter) {},
|
||||||
InjectSetResourceMapper: func(ResourceMapper) {},
|
InjectSetResourceMapper: func(data.ResourceMapper) {},
|
||||||
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) {},
|
||||||
InjectContentReaderStream: func() (*transport.Reader, error) { return nil, nil },
|
|
||||||
}
|
}
|
||||||
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,20 +3,18 @@
|
|||||||
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"
|
||||||
"decl/internal/transport"
|
"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:"-"`
|
||||||
@ -35,11 +33,9 @@ type MockResource struct {
|
|||||||
InjectUpdate func(context.Context) error `json:"-" yaml:"-"`
|
InjectUpdate func(context.Context) error `json:"-" yaml:"-"`
|
||||||
InjectDelete func(context.Context) error `json:"-" yaml:"-"`
|
InjectDelete func(context.Context) error `json:"-" yaml:"-"`
|
||||||
InjectStateMachine func() machine.Stater `json:"-" yaml:"-"`
|
InjectStateMachine func() machine.Stater `json:"-" yaml:"-"`
|
||||||
InjectSetResourceMapper func(ResourceMapper) `json:"-" yaml:"-"`
|
InjectSetResourceMapper func(data.ResourceMapper) `json:"-" yaml:"-"`
|
||||||
InjectUseConfig func(data.ConfigurationValueGetter) `json:"-" yaml:"-"`
|
InjectUseConfig func(data.ConfigurationValueGetter) `json:"-" yaml:"-"`
|
||||||
InjectNotify func(*machine.EventMessage) `json:"-" yaml:"-"`
|
InjectNotify func(*machine.EventMessage) `json:"-" yaml:"-"`
|
||||||
InjectContentReaderStream func() (*transport.Reader, error) `json:"-" yaml:"-"`
|
|
||||||
InjectContentWriterStream func() (*transport.Writer, error) `json:"-" yaml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) Clone() data.Resource {
|
func (m *MockResource) Clone() data.Resource {
|
||||||
@ -58,7 +54,7 @@ func (m *MockResource) SetURI(uri string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) SetParsedURI(uri data.URIParser) error {
|
func (m *MockResource) SetParsedURI(uri *url.URL) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +66,7 @@ func (m *MockResource) ResolveId(ctx context.Context) string {
|
|||||||
return m.InjectResolveId(ctx)
|
return m.InjectResolveId(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) SetResourceMapper(rm ResourceMapper) {
|
func (m *MockResource) SetResourceMapper(rm data.ResourceMapper) {
|
||||||
m.InjectSetResourceMapper(rm)
|
m.InjectSetResourceMapper(rm)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,10 +126,6 @@ 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()
|
||||||
}
|
}
|
||||||
@ -142,14 +134,6 @@ func (m *MockResource) Notify(em *machine.EventMessage) {
|
|||||||
m.InjectNotify(em)
|
m.InjectNotify(em)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) ContentReaderStream() (*transport.Reader, error) {
|
|
||||||
return m.InjectContentReaderStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockResource) ContentWriterStream() (*transport.Writer, error) {
|
|
||||||
return m.InjectContentWriterStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockResource) UnmarshalJSON(data []byte) error {
|
func (m *MockResource) UnmarshalJSON(data []byte) error {
|
||||||
if err := json.Unmarshal(data, m); err != nil {
|
if err := json.Unmarshal(data, m); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1,102 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
@ -1,229 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package folio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"decl/internal/mapper"
|
|
||||||
"decl/internal/data"
|
|
||||||
"decl/internal/transport"
|
|
||||||
"net/url"
|
|
||||||
"log/slog"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrRefMapperMismatch = errors.New("Ref type does not the provided mapper")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Ref struct {
|
|
||||||
Uri URI `json:"uri" yaml:"uri"`
|
|
||||||
RefType ReferenceType `json:"type,omitempty" yaml:"type,omitempty"`
|
|
||||||
documentMapper mapper.Store[URI, *Document]
|
|
||||||
resourceMapper mapper.Store[URI, *Declaration]
|
|
||||||
}
|
|
||||||
|
|
||||||
type decodeRef Ref
|
|
||||||
|
|
||||||
func NewRef() *Ref {
|
|
||||||
return &Ref {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ReferenceType) UnmarshalValue(value string) error {
|
|
||||||
switch value {
|
|
||||||
case string(ReferenceTypeResource), string(ReferenceTypeDocument):
|
|
||||||
*r = ReferenceType(value)
|
|
||||||
default:
|
|
||||||
*r = ReferenceTypeResource
|
|
||||||
//return ErrInvalidReferenceType
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ReferenceType) UnmarshalJSON(jsonData []byte) error {
|
|
||||||
var s string
|
|
||||||
if unmarshalReferenceTypeErr := json.Unmarshal(jsonData, &s); unmarshalReferenceTypeErr != nil {
|
|
||||||
return unmarshalReferenceTypeErr
|
|
||||||
}
|
|
||||||
return r.UnmarshalValue(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ReferenceType) UnmarshalYAML(value *yaml.Node) error {
|
|
||||||
var s string
|
|
||||||
if err := value.Decode(&s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.UnmarshalValue(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) UnmarshalValue(value *decodeRef) error {
|
|
||||||
slog.Info("Ref.UnmarshalValue", "decode", value)
|
|
||||||
r.Uri = value.Uri
|
|
||||||
switch value.RefType {
|
|
||||||
case ReferenceTypeResource:
|
|
||||||
r.RefType = value.RefType
|
|
||||||
case ReferenceTypeDocument:
|
|
||||||
r.RefType = value.RefType
|
|
||||||
r.SetMapper(DocumentRegistry.UriMap)
|
|
||||||
default:
|
|
||||||
r.RefType = ReferenceTypeResource
|
|
||||||
//return ErrInvalidReferenceType
|
|
||||||
}
|
|
||||||
slog.Info("Ref.UnmarshalValue", "stored", *r)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) UnmarshalJSON(jsonData []byte) error {
|
|
||||||
decodeJsonToRef := &decodeRef{}
|
|
||||||
if unmarshalReferenceTypeErr := json.Unmarshal(jsonData, decodeJsonToRef); unmarshalReferenceTypeErr != nil {
|
|
||||||
return unmarshalReferenceTypeErr
|
|
||||||
}
|
|
||||||
return r.UnmarshalValue(decodeJsonToRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) UnmarshalYAML(value *yaml.Node) error {
|
|
||||||
decodeYamlToRef := &decodeRef{}
|
|
||||||
if err := value.Decode(decodeYamlToRef); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.UnmarshalValue(decodeYamlToRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) SetMapper(m any) error {
|
|
||||||
var ok bool
|
|
||||||
switch r.RefType {
|
|
||||||
case ReferenceTypeResource:
|
|
||||||
if r.resourceMapper, ok = m.(mapper.Store[URI, *Declaration]); ! ok {
|
|
||||||
return fmt.Errorf("%w - %T is not a %s", ErrRefMapperMismatch, m, r.RefType)
|
|
||||||
}
|
|
||||||
case ReferenceTypeDocument:
|
|
||||||
if r.documentMapper, ok = m.(mapper.Store[URI, *Document]); ! ok {
|
|
||||||
return fmt.Errorf("%w - %T is not a %s", ErrRefMapperMismatch, m, r.RefType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Dereference[T ReferenceTypes](uri URI, look mapper.Map[URI, T]) T {
|
|
||||||
if uri != "" && look != nil {
|
|
||||||
if v, ok := look.Get(uri); ok {
|
|
||||||
slog.Info("Ref::Dereference()", "value", v, "mapper", look)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) Lookup(look any) ContentReadWriter {
|
|
||||||
slog.Info("Ref.Lookup()", "ref", r, "mapper", look)
|
|
||||||
switch r.RefType {
|
|
||||||
case ReferenceTypeResource:
|
|
||||||
if resourceDeclaration := Dereference(r.Uri, look.(mapper.Store[URI, *Declaration])); resourceDeclaration != nil {
|
|
||||||
return resourceDeclaration.GetContentReadWriter()
|
|
||||||
}
|
|
||||||
case ReferenceTypeDocument:
|
|
||||||
if document := Dereference(r.Uri, look.(mapper.Store[URI, *Document])); document != nil {
|
|
||||||
return document.GetContentReadWriter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.Uri
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) Dereference(look any) any {
|
|
||||||
switch r.RefType {
|
|
||||||
case ReferenceTypeResource:
|
|
||||||
if look == nil {
|
|
||||||
return Dereference(r.Uri, r.resourceMapper)
|
|
||||||
} else {
|
|
||||||
return Dereference(r.Uri, look.(mapper.Store[URI, *Declaration]))
|
|
||||||
}
|
|
||||||
case ReferenceTypeDocument:
|
|
||||||
if look == nil {
|
|
||||||
return Dereference(r.Uri, r.documentMapper)
|
|
||||||
} else {
|
|
||||||
return Dereference(r.Uri, look.(mapper.Store[URI, *Document]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) DereferenceDefault() any {
|
|
||||||
switch r.RefType {
|
|
||||||
case ReferenceTypeResource:
|
|
||||||
return Dereference(r.Uri, r.resourceMapper)
|
|
||||||
case ReferenceTypeDocument:
|
|
||||||
return Dereference(r.Uri, r.documentMapper)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) Parse() data.URIParser {
|
|
||||||
return r.Uri.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) Exists() bool {
|
|
||||||
return r.Uri.Exists()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) ContentReaderStream() (*transport.Reader, error) {
|
|
||||||
return r.Uri.ContentReaderStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) ContentWriterStream() (*transport.Writer, error) {
|
|
||||||
return r.Uri.ContentWriterStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) GetContent(w io.Writer) (contentReader io.ReadCloser, err error) {
|
|
||||||
target := r.DereferenceDefault()
|
|
||||||
if targetContent, ok := target.(data.ContentGetSetter); ok {
|
|
||||||
return targetContent.GetContent(w)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("Ref target does not support ContentGetSetter: %s, %s, %#v", r.RefType, r.Uri, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) SetContent(contentReader io.Reader) error {
|
|
||||||
target := r.DereferenceDefault()
|
|
||||||
if targetContent, ok := target.(data.ContentGetSetter); ok {
|
|
||||||
return targetContent.SetContent(contentReader)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Ref target does not support ContentGetSetter: %s, %s, %#v", r.RefType, r.Uri, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) Reader() (reader io.ReadCloser, err error) {
|
|
||||||
switch r.RefType {
|
|
||||||
case ReferenceTypeResource:
|
|
||||||
return r.ContentReaderStream()
|
|
||||||
case ReferenceTypeDocument:
|
|
||||||
reader, err = r.GetContent(nil)
|
|
||||||
if reader == nil {
|
|
||||||
return r.ContentReaderStream()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return nil, ErrInvalidReferenceType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) String() string {
|
|
||||||
return r.Uri.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) SetURL(url *url.URL) {
|
|
||||||
r.Uri.SetURL(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) Extension() (string, string) {
|
|
||||||
return r.Uri.Extension()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) IsEmpty() bool {
|
|
||||||
return r.Uri.IsEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Ref) FindIn(s *SearchPath) {
|
|
||||||
r.Uri.FindIn(s)
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package folio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
"fmt"
|
|
||||||
_ "decl/internal/data"
|
|
||||||
"decl/internal/mapper"
|
|
||||||
"decl/internal/codec"
|
|
||||||
"log/slog"
|
|
||||||
"strings"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReference(t *testing.T) {
|
|
||||||
f := NewFooResource()
|
|
||||||
resourceMapper := mapper.New[URI, *Declaration]()
|
|
||||||
f.Name = string(TempDir)
|
|
||||||
f.Size = 10
|
|
||||||
f.MockResource.InjectURI = func() string { return fmt.Sprintf("%s://%s", "foo", f.Name) }
|
|
||||||
d := NewDeclaration()
|
|
||||||
d.Type = "foo"
|
|
||||||
d.Attributes = f
|
|
||||||
resourceMapper[URI(d.URI())] = d
|
|
||||||
slog.Info("TestReference", "declaration", d, "mapper", resourceMapper)
|
|
||||||
|
|
||||||
var fooRef *Ref = NewRef()
|
|
||||||
fooRef.RefType = ReferenceTypeResource
|
|
||||||
fooRef.Uri = URI(fmt.Sprintf("foo://%s", string(TempDir)))
|
|
||||||
u := fooRef.Uri.Parse().URL()
|
|
||||||
assert.Equal(t, "foo", u.Scheme)
|
|
||||||
assert.True(t, fooRef.Uri.Exists())
|
|
||||||
|
|
||||||
fromRef := fooRef.Lookup(resourceMapper)
|
|
||||||
assert.NotNil(t, fromRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDocumentRef(t *testing.T) {
|
|
||||||
docUri := fmt.Sprintf("file://%s/doc.yaml", TempDir)
|
|
||||||
DocumentRegistry.ResourceTypes = TestResourceTypes
|
|
||||||
document := `
|
|
||||||
---
|
|
||||||
resources:
|
|
||||||
- type: foo
|
|
||||||
attributes:
|
|
||||||
name: "testfoo"
|
|
||||||
size: 10022
|
|
||||||
`
|
|
||||||
TempDir.CreateFile("doc.yaml", document)
|
|
||||||
|
|
||||||
d := DocumentRegistry.NewDocument(URI(docUri))
|
|
||||||
assert.NotNil(t, d)
|
|
||||||
docReader := io.NopCloser(strings.NewReader(document))
|
|
||||||
|
|
||||||
e := d.LoadReader(docReader, codec.FormatYaml)
|
|
||||||
assert.Nil(t, e)
|
|
||||||
|
|
||||||
var docRef *Ref = NewRef()
|
|
||||||
docRef.RefType = ReferenceTypeDocument
|
|
||||||
docRef.Uri = URI(docUri)
|
|
||||||
u := docRef.Uri.Parse().URL()
|
|
||||||
assert.Equal(t, "file", u.Scheme)
|
|
||||||
|
|
||||||
assert.Equal(t, d, docRef.Dereference(DocumentRegistry.UriMap).(*Document))
|
|
||||||
}
|
|
@ -1,133 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package folio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"net/url"
|
|
||||||
"decl/internal/data"
|
|
||||||
"decl/internal/mapper"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrInvalidURI error = errors.New("Invalid URI")
|
|
||||||
ErrInvalidReferenceType error = errors.New("invalid ReferenceType value")
|
|
||||||
)
|
|
||||||
|
|
||||||
type ReferenceTypes interface {
|
|
||||||
*Document | *Declaration
|
|
||||||
}
|
|
||||||
|
|
||||||
type RefMap[T ReferenceTypes] mapper.Store[string, T]
|
|
||||||
|
|
||||||
type ReferenceMapper[T ReferenceTypes] interface {
|
|
||||||
mapper.Map[URI, T]
|
|
||||||
}
|
|
||||||
type ResourceMapper ReferenceMapper[*Declaration]
|
|
||||||
type DocumentMapper ReferenceMapper[*Document]
|
|
||||||
|
|
||||||
type ReferenceType string
|
|
||||||
type RefType[T ReferenceTypes, TypedReferenceMapper ReferenceMapper[T]] ReferenceType
|
|
||||||
|
|
||||||
type ReferenceURI[T ReferenceTypes] URI
|
|
||||||
type DocumentURI ReferenceURI[*Document]
|
|
||||||
type ResourceURI ReferenceURI[*Declaration]
|
|
||||||
|
|
||||||
const (
|
|
||||||
ReferenceTypeResource ReferenceType = "resource"
|
|
||||||
RefTypeResource RefType[*Declaration, mapper.Store[URI, *Declaration]] = RefType[*Declaration, mapper.Store[URI, *Declaration]](ReferenceTypeResource)
|
|
||||||
ReferenceTypeDocument ReferenceType = "document"
|
|
||||||
RefTypeDocument RefType[*Document, mapper.Store[URI, *Document]] = RefType[*Document, mapper.Store[URI, *Document]](ReferenceTypeDocument)
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommonReferrer interface {
|
|
||||||
ReferenceType() ReferenceType
|
|
||||||
Parse() *url.URL
|
|
||||||
Exists() bool
|
|
||||||
data.ContentReadWriter
|
|
||||||
IsEmpty() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// interface for a reference
|
|
||||||
type Referrer[T ReferenceTypes, TypedReferenceMapper ReferenceMapper[T]] interface {
|
|
||||||
CommonReferrer
|
|
||||||
Lookup(TypedReferenceMapper) ContentReadWriter
|
|
||||||
GetContentReadWriter(TypedReferenceMapper) ContentReadWriter
|
|
||||||
Dereference(TypedReferenceMapper) T
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func (r ReferenceType) URIType() any {
|
|
||||||
switch r {
|
|
||||||
case ReferenceTypeDocument:
|
|
||||||
return ReferenceURI[*Document]
|
|
||||||
case ReferenceTypeResource:
|
|
||||||
return ReferenceURI[*Declaration]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (r *RefType[T, TypedReferenceMapper]) UnmarshalValue(value string) error {
|
|
||||||
switch value {
|
|
||||||
case string(ReferenceTypeResource), string(ReferenceTypeDocument):
|
|
||||||
*r = RefType[T, TypedReferenceMapper](value)
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return ErrInvalidReferenceType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RefType[T, TypedReferenceMapper]) UnmarshalJSON(data []byte) error {
|
|
||||||
var s string
|
|
||||||
if unmarshalReferenceTypeErr := json.Unmarshal(data, &s); unmarshalReferenceTypeErr != nil {
|
|
||||||
return unmarshalReferenceTypeErr
|
|
||||||
}
|
|
||||||
return r.UnmarshalValue(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RefType[T, TypedReferenceMapper]) UnmarshalYAML(value *yaml.Node) error {
|
|
||||||
var s string
|
|
||||||
if err := value.Decode(&s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.UnmarshalValue(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RefType[T, TypedReferenceMapper]) Dereference(uri URI, look TypedReferenceMapper) (result T) {
|
|
||||||
result = nil
|
|
||||||
if uri != "" {
|
|
||||||
if v,ok := look.Get(uri); ok {
|
|
||||||
result = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func RefURI[T ReferenceTypes, RT RefType[T, ReferenceMapper[T]], RU ReferenceURI[T]](uri URI, reftype RT) (refuri RU) {
|
|
||||||
refuri = RU(uri)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func (r *ReferenceURI[T]) GetContentReadWriter(mapper TypedReferenceMapper) ContentReadWriter {
|
|
||||||
if URI(r).String() == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if mapper != nil {
|
|
||||||
if v,ok := mapper.Get(string(r)); ok {
|
|
||||||
return v.(data.Originator).GetContentReadWriter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
type ResourceMapSetter interface {
|
|
||||||
SetResourceMapper(ResourceMapper)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewResourceMapper() ResourceMapper {
|
|
||||||
return mapper.New[URI, *Declaration]()
|
|
||||||
}
|
|
@ -60,8 +60,7 @@ func (r *Registry) HasDocument(key URI) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Registry) GetDocument(key URI) (*Document, bool) {
|
func (r *Registry) GetDocument(key URI) (*Document, bool) {
|
||||||
document, result := r.UriMap.Get(key)
|
return r.UriMap.Get(key)
|
||||||
return document, result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Registry) SetDocument(key URI, value *Document) {
|
func (r *Registry) SetDocument(key URI, value *Document) {
|
||||||
@ -70,10 +69,10 @@ 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.SetURI(string(uri))
|
doc.URI = uri
|
||||||
r.Documents = append(r.Documents, doc)
|
r.Documents = append(r.Documents, doc)
|
||||||
if uri != "" {
|
if uri != "" {
|
||||||
r.UriMap.Set(uri, doc)
|
r.UriMap[uri] = doc
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -89,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.AppendParsedURI() - ExtractMany", "uri", uri, "source", sourceResource, "docs", docs, "error", err)
|
slog.Info("folio.Registry.Append() - 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.AppendParsedURI() - 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slog.Info("folio.Registry.AppendParsedURI()", "uri", uri, "converter", r.ConverterTypes, "error", err)
|
slog.Info("folio.Registry.Append()", "uri", uri, "converter", r.ConverterTypes, "error", err)
|
||||||
addedDocuments = documents
|
addedDocuments = documents
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -131,8 +130,6 @@ 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)
|
||||||
@ -149,8 +146,3 @@ 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((*ParsedURI)(u)); err == nil {
|
if err = declaration.NewResourceFromParsedURI(u); err == nil {
|
||||||
return declaration.Attributes, err
|
return declaration.Attributes, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newResource, err = document.NewResourceFromParsedURI((*ParsedURI)(u))
|
newResource, err = document.NewResourceFromParsedURI(u)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -5,7 +5,6 @@ package folio
|
|||||||
import (
|
import (
|
||||||
"decl/internal/transport"
|
"decl/internal/transport"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/mapper"
|
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -32,33 +31,32 @@ type ContentReadWriter interface {
|
|||||||
type ResourceReference URI
|
type ResourceReference URI
|
||||||
|
|
||||||
// Return a Content ReadWriter for the resource referred to.
|
// Return a Content ReadWriter for the resource referred to.
|
||||||
func (r ResourceReference) Lookup(look mapper.Map[URI, *Declaration]) ContentReadWriter {
|
func (r ResourceReference) Lookup(look data.ResourceMapper) ContentReadWriter {
|
||||||
if string(r) == "" {
|
if string(r) == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
slog.Info("ResourceReference.Lookup()", "resourcereference", r, "resourcemapper", look)
|
slog.Info("ResourceReference.Lookup()", "resourcereference", r, "resourcemapper", look)
|
||||||
if look != nil {
|
if look != nil {
|
||||||
if v,ok := look.Get(URI(r)); ok {
|
if v,ok := look.Get(string(r)); ok {
|
||||||
slog.Info("ResourceReference.Lookup()", "resourcereference", r, "result", v.Resource())
|
return v.(ContentReadWriter)
|
||||||
return v.Resource().(ContentReadWriter)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r ResourceReference) Dereference(look mapper.Map[URI, *Declaration]) data.Resource {
|
func (r ResourceReference) Dereference(look data.ResourceMapper) data.Resource {
|
||||||
slog.Info("ResourceReference.Dereference()", "resourcereference", r, "resourcemapper", look)
|
slog.Info("ResourceReference.Dereference()", "resourcereference", r, "resourcemapper", look)
|
||||||
if look != nil {
|
if look != nil {
|
||||||
if v,ok := look.Get(URI(r)); ok {
|
if v,ok := look.Get(string(r)); ok {
|
||||||
slog.Info("ResourceReference.Dereference()", "resourcereference", r, "result", v)
|
slog.Info("ResourceReference.Dereference()", "resourcereference", r, "result", v)
|
||||||
return v.Attributes
|
return v.(*Declaration).Attributes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r ResourceReference) Parse() *url.URL {
|
func (r ResourceReference) Parse() *url.URL {
|
||||||
return URI(r).Parse().URL()
|
return URI(r).Parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r ResourceReference) Exists() bool {
|
func (r ResourceReference) Exists() bool {
|
||||||
@ -72,11 +70,3 @@ func (r ResourceReference) ContentReaderStream() (*transport.Reader, error) {
|
|||||||
func (r ResourceReference) ContentWriterStream() (*transport.Writer, error) {
|
func (r ResourceReference) ContentWriterStream() (*transport.Writer, error) {
|
||||||
return URI(r).ContentWriterStream()
|
return URI(r).ContentWriterStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r ResourceReference) IsEmpty() bool {
|
|
||||||
return URI(r).IsEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResourceReference) FindIn(s *SearchPath) {
|
|
||||||
(*URI)(r).FindIn(s)
|
|
||||||
}
|
|
||||||
|
@ -6,18 +6,19 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"decl/internal/data"
|
||||||
"decl/internal/mapper"
|
"decl/internal/mapper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResourceReference(t *testing.T) {
|
func TestResourceReference(t *testing.T) {
|
||||||
f := NewFooResource()
|
f := NewFooResource()
|
||||||
resourceMapper := mapper.New[URI, *Declaration]()
|
resourceMapper := mapper.New[string, data.Declaration]()
|
||||||
f.Name = string(TempDir)
|
f.Name = string(TempDir)
|
||||||
f.Size = 10
|
f.Size = 10
|
||||||
d := NewDeclaration()
|
d := NewDeclaration()
|
||||||
d.Type = "foo"
|
d.Type = "foo"
|
||||||
d.Attributes = f
|
d.Attributes = f
|
||||||
resourceMapper[URI(d.URI())] = d
|
resourceMapper[d.URI()] = d
|
||||||
|
|
||||||
var foo ResourceReference = ResourceReference(fmt.Sprintf("foo://%s", string(TempDir)))
|
var foo ResourceReference = ResourceReference(fmt.Sprintf("foo://%s", string(TempDir)))
|
||||||
u := foo.Parse()
|
u := foo.Parse()
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "foo name",
|
"description": "foo name",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"$id": "ref.schema.json",
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "ref",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Type of object referred to",
|
|
||||||
"enum": [ "resource", "document" ]
|
|
||||||
},
|
|
||||||
"uri": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "URI of the object referred to"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
// 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())
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
// 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,35 +35,18 @@ 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() data.URIParser {
|
func (u URI) Parse() *url.URL {
|
||||||
url, e := url.Parse(string(u))
|
url, e := url.Parse(string(u))
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return (*ParsedURI)(url)
|
return url
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *URI) FindIn(s *SearchPath) {
|
|
||||||
if s == nil {
|
|
||||||
s = NewSearchPath(ConfigKey("system.importpath").GetStringSlice())
|
|
||||||
}
|
|
||||||
*u = s.FindURI(*u)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u URI) Exists() bool {
|
func (u URI) Exists() bool {
|
||||||
return transport.ExistsURI(string(u))
|
return transport.ExistsURI(string(u))
|
||||||
}
|
}
|
||||||
|
@ -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().URL()
|
u := file.Parse()
|
||||||
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))
|
||||||
x := file.Parse()
|
u = file.Parse()
|
||||||
assert.Nil(t, x)
|
assert.Nil(t, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
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().URL()
|
u := file.Parse()
|
||||||
var fileFromURL URI
|
var fileFromURL URI
|
||||||
fileFromURL.SetURL(u)
|
fileFromURL.SetURL(u)
|
||||||
assert.Equal(t, fileFromURL, file)
|
assert.Equal(t, fileFromURL, file)
|
||||||
@ -37,13 +37,3 @@ func TestURINewResource(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestURIFindIn(t *testing.T) {
|
|
||||||
searchPath := NewSearchPath(ConfigKey("system.importpath").GetStringSlice())
|
|
||||||
assert.Nil(t, searchPath.AddPath(string(TempDir)))
|
|
||||||
assert.Nil(t, TempDir.CreateFile("bar", "testdata"))
|
|
||||||
|
|
||||||
var file URI = "file://bar"
|
|
||||||
file.FindIn(searchPath)
|
|
||||||
assert.True(t, file.Exists())
|
|
||||||
assert.Equal(t, string(file), fmt.Sprintf("file://%s/bar", TempDir))
|
|
||||||
}
|
|
||||||
|
@ -18,15 +18,10 @@ 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 {
|
||||||
@ -38,7 +33,6 @@ 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] {
|
||||||
|
129
internal/resource/command.go
Normal file
129
internal/resource/command.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
58
internal/resource/command_test.go
Normal file
58
internal/resource/command_test.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
@ -9,22 +9,18 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/folio"
|
"decl/internal/folio"
|
||||||
_ "decl/internal/mapper"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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:"-"`
|
||||||
@ -32,14 +28,12 @@ type Common struct {
|
|||||||
|
|
||||||
State string `json:"state,omitempty" yaml:"state,omitempty"`
|
State string `json:"state,omitempty" yaml:"state,omitempty"`
|
||||||
config data.ConfigurationValueGetter
|
config data.ConfigurationValueGetter
|
||||||
Resources folio.ResourceMapper `json:"-" yaml:"-"`
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||||
Errors []error `json:"-" yaml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,19 +48,15 @@ func (c *Common) ContentType() string {
|
|||||||
return c.exttype
|
return c.exttype
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) SetResourceMapper(resources folio.ResourceMapper) {
|
func (c *Common) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
c.Resources = resources
|
c.Resources = resources
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) Clone() *Common {
|
func (c *Common) Clone() *Common {
|
||||||
return &Common {
|
return &Common {
|
||||||
SchemeCheck: c.SchemeCheck,
|
Uri: c.Uri,
|
||||||
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,
|
||||||
@ -84,40 +74,41 @@ func (c *Common) URIPath() string {
|
|||||||
return c.Path
|
return c.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) URI() folio.URI {
|
func (c *Common) URI() string {
|
||||||
slog.Info("Common.URI", "parsed", c.parsedURI)
|
return string(c.Uri)
|
||||||
return folio.URI(c.parsedURI.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) SetParsedURI(u data.URIParser) (err error) {
|
func (c *Common) SetURI(uri string) (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)
|
||||||
slog.Info("Common.SetParsedURI()", "parsed", u, "uri", c)
|
if c.Uri.IsEmpty() {
|
||||||
|
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.config != nil {
|
|
||||||
if prefixPath, configErr := c.config.GetValue("prefix"); configErr == nil {
|
|
||||||
c.Path = filepath.Join(prefixPath.(string), c.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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,41 +117,26 @@ func (c *Common) UseConfig(config data.ConfigurationValueGetter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) ResolveId(ctx context.Context) string {
|
func (c *Common) ResolveId(ctx context.Context) string {
|
||||||
var err error
|
if e := c.NormalizePath(); e != nil {
|
||||||
if c.absPath, err = filepath.Abs(c.Path); err != nil {
|
panic(e)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if err = c.NormalizePath(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
return c.Path
|
return c.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common path normalization for a file resource.
|
func (c *Common) NormalizePath() error {
|
||||||
func (c *Common) NormalizeFilePath() (err error) {
|
if c.config != nil {
|
||||||
if c.normalizePath {
|
if prefixPath, configErr := c.config.GetValue("prefix"); configErr == nil {
|
||||||
c.Path = c.absPath
|
c.Path = filepath.Join(prefixPath.(string), c.Path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
if c.normalizePath {
|
||||||
|
filePath, fileAbsErr := filepath.Abs(c.Path)
|
||||||
|
if fileAbsErr == nil {
|
||||||
|
c.Path = filePath
|
||||||
|
}
|
||||||
|
return fileAbsErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) Type() string { return string(c.resourceType) }
|
func (c *Common) Type() string { return string(c.resourceType) }
|
||||||
|
|
||||||
|
|
||||||
// If a resource update has errors but the resource is not actually absent
|
|
||||||
func (c *Common) IsResourceInconsistent() (result bool) {
|
|
||||||
for _, err := range c.Errors {
|
|
||||||
if ! errors.Is(err, ErrResourceStateAbsent) {
|
|
||||||
result = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Common) AddError(err error) (error) {
|
|
||||||
if err != nil {
|
|
||||||
slog.Info("Common.AddError()", "errors", c.Errors, "err", err)
|
|
||||||
c.Errors = append(c.Errors, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
@ -6,7 +6,6 @@ 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) {
|
||||||
@ -17,15 +16,16 @@ 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 folio.URI; expected *Common }{
|
for _, v := range []struct{ uri string; 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.SetParsedURI(v.uri.Parse()))
|
assert.Nil(t, c.SetURI(v.uri))
|
||||||
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,87 +28,74 @@ _ "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"
|
|
||||||
"decl/internal/containerlog"
|
|
||||||
"bytes"
|
|
||||||
_ "encoding/base64"
|
|
||||||
"time"
|
|
||||||
"errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ContainerTypeName TypeName = "container"
|
ContainerTypeName TypeName = "container"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ErrContainerWaitTimeOut = errors.New("Container wait timed out waiting for state")
|
|
||||||
)
|
|
||||||
|
|
||||||
type ContainerClient interface {
|
type ContainerClient interface {
|
||||||
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
|
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
|
||||||
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error
|
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error
|
||||||
ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error
|
|
||||||
ContainerList(context.Context, container.ListOptions) ([]types.Container, error)
|
ContainerList(context.Context, container.ListOptions) ([]types.Container, error)
|
||||||
ContainerInspect(context.Context, string) (types.ContainerJSON, error)
|
ContainerInspect(context.Context, string) (types.ContainerJSON, error)
|
||||||
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
||||||
ContainerStop(context.Context, string, container.StopOptions) error
|
ContainerStop(context.Context, string, container.StopOptions) error
|
||||||
ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error)
|
ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error)
|
||||||
ContainerLogs(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error)
|
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
*Common `yaml:",inline" json:",inline"`
|
*Common `yaml:",inline" json:",inline"`
|
||||||
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"`
|
||||||
Cmd []string `json:"cmd,omitempty" yaml:"cmd,omitempty"`
|
// Path string `json:"path" yaml:"path"`
|
||||||
WorkingDir string `json:"workingdir,omitempty" yaml:"workingdir,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"`
|
||||||
Ports []string `json:"ports,omitempty" yaml:"ports,omitempty"`
|
Ports []string `json:"ports,omitempty" yaml:"ports,omitempty"`
|
||||||
Environment map[string]string `json:"environment" yaml:"environment"`
|
Environment map[string]string `json:"environment" yaml:"environment"`
|
||||||
Image string `json:"image" yaml:"image"`
|
Image string `json:"image" yaml:"image"`
|
||||||
ResolvConfPath string `json:"resolvconfpath" yaml:"resolvconfpath"`
|
ResolvConfPath string `json:"resolvconfpath" yaml:"resolvconfpath"`
|
||||||
HostnamePath string `json:"hostnamepath" yaml:"hostnamepath"`
|
HostnamePath string `json:"hostnamepath" yaml:"hostnamepath"`
|
||||||
HostsPath string `json:"hostpath" yaml:"hostspath"`
|
HostsPath string `json:"hostpath" yaml:"hostspath"`
|
||||||
LogPath string `json:"logpath" yaml:"logpath"`
|
LogPath string `json:"logpath" yaml:"logpath"`
|
||||||
Created string `json:"created" yaml:"created"`
|
Created string `json:"created" yaml:"created"`
|
||||||
ContainerState types.ContainerState `json:"containerstate" yaml:"containerstate"`
|
ContainerState types.ContainerState `json:"containerstate" yaml:"containerstate"`
|
||||||
RestartCount int `json:"restartcount" yaml:"restartcount"`
|
RestartCount int `json:"restartcount" yaml:"restartcount"`
|
||||||
Driver string `json:"driver" yaml:"driver"`
|
Driver string `json:"driver" yaml:"driver"`
|
||||||
Platform string `json:"platform" yaml:"platform"`
|
Platform string `json:"platform" yaml:"platform"`
|
||||||
MountLabel string `json:"mountlabel" yaml:"mountlabel"`
|
MountLabel string `json:"mountlabel" yaml:"mountlabel"`
|
||||||
ProcessLabel string `json:"processlabel" yaml:"processlabel"`
|
ProcessLabel string `json:"processlabel" yaml:"processlabel"`
|
||||||
AppArmorProfile string `json:"apparmorprofile" yaml:"apparmorprofile"`
|
AppArmorProfile string `json:"apparmorprofile" yaml:"apparmorprofile"`
|
||||||
ExecIDs []string `json:"execids" yaml:"execids"`
|
ExecIDs []string `json:"execids" yaml:"execids"`
|
||||||
HostConfig container.HostConfig `json:"hostconfig" yaml:"hostconfig"`
|
HostConfig container.HostConfig `json:"hostconfig" yaml:"hostconfig"`
|
||||||
GraphDriver types.GraphDriverData `json:"graphdriver" yaml:"graphdriver"`
|
GraphDriver types.GraphDriverData `json:"graphdriver" yaml:"graphdriver"`
|
||||||
SizeRw *int64 `json:",omitempty" yaml:",omitempty"`
|
SizeRw *int64 `json:",omitempty" yaml:",omitempty"`
|
||||||
SizeRootFs *int64 `json:",omitempty" yaml:",omitempty"`
|
SizeRootFs *int64 `json:",omitempty" yaml:",omitempty"`
|
||||||
Networks []string `json:"networks,omitempty" yaml:"networks,omitempty"`
|
Networks []string `json:"networks,omitempty" yaml:"networks,omitempty"`
|
||||||
/*
|
/*
|
||||||
Mounts []MountPoint
|
Mounts []MountPoint
|
||||||
Config *container.Config
|
Config *container.Config
|
||||||
NetworkSettings *NetworkSettings
|
NetworkSettings *NetworkSettings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Wait bool `json:"wait,omitempty" yaml:"wait,omitempty"`
|
// State string `yaml:"state,omitempty" json:"state,omitempty"`
|
||||||
Stdout string `json:"stdout,omitempty" yaml:"stdout,omitempty"`
|
|
||||||
Stderr string `json:"stderr,omitempty" yaml:"stderr,omitempty"`
|
|
||||||
|
|
||||||
|
// config ConfigurationValueGetter
|
||||||
apiClient ContainerClient
|
apiClient ContainerClient
|
||||||
|
// Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ResourceTypes.Register([]string{"container"}, func(u *url.URL) (c data.Resource) {
|
ResourceTypes.Register([]string{"container"}, func(u *url.URL) data.Resource {
|
||||||
c = NewContainer(nil)
|
c := NewContainer(nil)
|
||||||
if u != nil {
|
c.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
if err := folio.CastParsedURI(u).ConstructResource(c); err != nil {
|
if err := c.Common.SetParsedURI(u); err == nil {
|
||||||
panic(err)
|
return c
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,27 +114,15 @@ func NewContainer(containerClientApi ContainerClient) *Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) Init(u data.URIParser) error {
|
func (c *Container) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
if u == nil {
|
c.Resources = resources
|
||||||
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) Clone() data.Resource {
|
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.Clone(),
|
Common: c.Common,
|
||||||
Cmd: c.Cmd,
|
Cmd: c.Cmd,
|
||||||
Entrypoint: c.Entrypoint,
|
Entrypoint: c.Entrypoint,
|
||||||
Args: c.Args,
|
Args: c.Args,
|
||||||
@ -171,6 +146,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,6 +158,10 @@ 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)
|
||||||
}
|
}
|
||||||
@ -191,22 +171,17 @@ func (c *Container) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) Notify(m *machine.EventMessage) {
|
func (c *Container) Notify(m *machine.EventMessage) {
|
||||||
slog.Info("Container.Notify()", "destination_event", m.Dest, "uri", c.URI())
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
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 := c.ReadStat(); statErr == nil {
|
if statErr := c.ReadStat(); statErr == nil {
|
||||||
slog.Info("Container.Notify() - ReadStat", "event", "start_stat", "error", statErr)
|
|
||||||
if triggerErr := c.StateMachine().Trigger("exists"); triggerErr == nil {
|
if triggerErr := c.StateMachine().Trigger("exists"); triggerErr == nil {
|
||||||
slog.Info("Container.Notify()", "event", "start_stat", "trigger", "exists")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
slog.Info("Container.Notify() - ReadStat", "event", "start_stat", "error", statErr)
|
|
||||||
if triggerErr := c.StateMachine().Trigger("notexists"); triggerErr == nil {
|
if triggerErr := c.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
slog.Info("Container.Notify()", "event", "start_stat", "trigger", "notexists")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -246,26 +221,6 @@ func (c *Container) Notify(m *machine.EventMessage) {
|
|||||||
c.Common.State = "present"
|
c.Common.State = "present"
|
||||||
panic(deleteErr)
|
panic(deleteErr)
|
||||||
}
|
}
|
||||||
case "restarting":
|
|
||||||
if restartErr := c.Restart(ctx); restartErr == nil {
|
|
||||||
if triggerErr := c.StateMachine().Trigger("restarted"); triggerErr == nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
_ = c.AddError(triggerErr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_ = c.AddError(restartErr)
|
|
||||||
if c.IsResourceInconsistent() {
|
|
||||||
if triggerErr := c.StateMachine().Trigger("restart-failed"); triggerErr == nil {
|
|
||||||
panic(restartErr)
|
|
||||||
} else {
|
|
||||||
_ = c.AddError(triggerErr)
|
|
||||||
panic(fmt.Errorf("%w - %w", restartErr, triggerErr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = c.StateMachine().Trigger("notexists")
|
|
||||||
panic(restartErr)
|
|
||||||
}
|
|
||||||
case "present", "created", "read":
|
case "present", "created", "read":
|
||||||
c.Common.State = "present"
|
c.Common.State = "present"
|
||||||
case "running":
|
case "running":
|
||||||
@ -278,24 +233,6 @@ func (c *Container) Notify(m *machine.EventMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) ReadStat() (err error) {
|
func (c *Container) ReadStat() (err error) {
|
||||||
err = fmt.Errorf("%w: %s", ErrResourceStateAbsent, c.Name)
|
|
||||||
filterArgs := filters.NewArgs()
|
|
||||||
filterArgs.Add("name", "/"+c.Name)
|
|
||||||
if containers, listErr := c.apiClient.ContainerList(context.Background(), container.ListOptions{
|
|
||||||
All: true,
|
|
||||||
Filters: filterArgs,
|
|
||||||
}); listErr == nil {
|
|
||||||
for _, container := range containers {
|
|
||||||
for _, containerName := range container.Names {
|
|
||||||
if containerName == "/"+c.Name {
|
|
||||||
slog.Info("Container.ReadStat() exists", "container", c.Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("%w: %w", err, listErr)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,61 +266,6 @@ func (c *Container) LoadDecl(yamlResourceDeclaration string) error {
|
|||||||
return c.LoadString(yamlResourceDeclaration, codec.FormatYaml)
|
return c.LoadString(yamlResourceDeclaration, codec.FormatYaml)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) ReadFromContainer(ctx context.Context) (err error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
var stdout, stderr []string
|
|
||||||
|
|
||||||
if stdoutReader, err := c.apiClient.ContainerLogs(ctx, c.Id, container.LogsOptions{ShowStdout: true, ShowStderr: true}); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
defer stdoutReader.Close()
|
|
||||||
|
|
||||||
if _, copyErr := io.Copy(&buf, stdoutReader); copyErr != nil {
|
|
||||||
return copyErr
|
|
||||||
}
|
|
||||||
slog.Info("Container.ReadFromContainer() - ContainerLogs", "read", buf.String())
|
|
||||||
|
|
||||||
for {
|
|
||||||
if streamType, message, extractErr := containerlog.Read(&buf); extractErr == nil {
|
|
||||||
switch streamType {
|
|
||||||
case containerlog.StreamStdout:
|
|
||||||
stdout = append(stdout, message)
|
|
||||||
case containerlog.StreamStderr:
|
|
||||||
stderr = append(stderr, message)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if extractErr == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
err = extractErr
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
if streamType, size, extractErr := c.ExtractLogHeader(&buf); extractErr == nil {
|
|
||||||
slog.Info("Container.Create() - ContainerLogs", "streamtype", streamType, "size", size)
|
|
||||||
var logMessage string
|
|
||||||
if logMessage, err = c.ReadLogMessage(&buf, size); err == nil {
|
|
||||||
switch streamType {
|
|
||||||
case ContainerLogStreamStdout:
|
|
||||||
stdout = append(stdout, logMessage)
|
|
||||||
case ContainerLogStreamStderr:
|
|
||||||
stderr = append(stderr, logMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if extractErr == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
err = extractErr
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.Stdout = strings.Join(stdout, "")
|
|
||||||
c.Stderr = strings.Join(stderr, "")
|
|
||||||
slog.Info("Container.ReadFromContainer()", "stdout", c.Stdout, "stderr", c.Stderr, "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Create(ctx context.Context) error {
|
func (c *Container) Create(ctx context.Context) error {
|
||||||
numberOfEnvironmentVariables := len(c.Environment)
|
numberOfEnvironmentVariables := len(c.Environment)
|
||||||
|
|
||||||
@ -397,9 +279,6 @@ func (c *Container) Create(ctx context.Context) error {
|
|||||||
Entrypoint: c.Entrypoint,
|
Entrypoint: c.Entrypoint,
|
||||||
Tty: false,
|
Tty: false,
|
||||||
ExposedPorts: portset,
|
ExposedPorts: portset,
|
||||||
WorkingDir: c.WorkingDir,
|
|
||||||
AttachStdout: true,
|
|
||||||
AttachStderr: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Env = make([]string, numberOfEnvironmentVariables)
|
config.Env = make([]string, numberOfEnvironmentVariables)
|
||||||
@ -431,32 +310,20 @@ func (c *Container) Create(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
c.Id = resp.ID
|
c.Id = resp.ID
|
||||||
|
|
||||||
|
/*
|
||||||
|
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
case <-statusCh:
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
if startErr := c.apiClient.ContainerStart(ctx, c.Id, container.StartOptions{}); startErr != nil {
|
if startErr := c.apiClient.ContainerStart(ctx, c.Id, container.StartOptions{}); startErr != nil {
|
||||||
return startErr
|
return startErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = c.ReadFromContainer(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Wait {
|
|
||||||
slog.Info("Container.Create() - waiting for container to stop", "id", c.Id, "name", c.Name)
|
|
||||||
statusCh, errCh := c.apiClient.ContainerWait(ctx, c.Id, container.WaitConditionNotRunning)
|
|
||||||
select {
|
|
||||||
case err := <-errCh:
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
case <-statusCh:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.Stdout) == 0 && len(c.Stderr) == 0 {
|
|
||||||
if err = c.ReadFromContainer(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,10 +363,10 @@ func (c *Container) Read(ctx context.Context) ([]byte, error) {
|
|||||||
|
|
||||||
func (c *Container) Inspect(ctx context.Context, containerID string) error {
|
func (c *Container) Inspect(ctx context.Context, containerID string) error {
|
||||||
containerJSON, err := c.apiClient.ContainerInspect(ctx, containerID)
|
containerJSON, err := c.apiClient.ContainerInspect(ctx, containerID)
|
||||||
if client.IsErrNotFound(err) || containerJSON.State == nil {
|
if client.IsErrNotFound(err) {
|
||||||
c.Common.State = "absent"
|
c.Common.State = "absent"
|
||||||
} else {
|
} else {
|
||||||
c.Common.State = containerJSON.State.Status
|
c.Common.State = "present"
|
||||||
c.Id = containerJSON.ID
|
c.Id = containerJSON.ID
|
||||||
if c.Name == "" {
|
if c.Name == "" {
|
||||||
if containerJSON.Name[0] == '/' {
|
if containerJSON.Name[0] == '/' {
|
||||||
@ -510,6 +377,9 @@ func (c *Container) Inspect(ctx context.Context, containerID string) error {
|
|||||||
}
|
}
|
||||||
c.Common.Path = containerJSON.Path
|
c.Common.Path = containerJSON.Path
|
||||||
c.Image = containerJSON.Image
|
c.Image = containerJSON.Image
|
||||||
|
if containerJSON.State != nil {
|
||||||
|
c.ContainerState = *containerJSON.State
|
||||||
|
}
|
||||||
c.Created = containerJSON.Created
|
c.Created = containerJSON.Created
|
||||||
c.ResolvConfPath = containerJSON.ResolvConfPath
|
c.ResolvConfPath = containerJSON.ResolvConfPath
|
||||||
c.HostnamePath = containerJSON.HostnamePath
|
c.HostnamePath = containerJSON.HostnamePath
|
||||||
@ -517,18 +387,11 @@ func (c *Container) Inspect(ctx context.Context, containerID string) error {
|
|||||||
c.LogPath = containerJSON.LogPath
|
c.LogPath = containerJSON.LogPath
|
||||||
c.RestartCount = containerJSON.RestartCount
|
c.RestartCount = containerJSON.RestartCount
|
||||||
c.Driver = containerJSON.Driver
|
c.Driver = containerJSON.Driver
|
||||||
if containerJSON.State != nil {
|
|
||||||
c.ContainerState = *containerJSON.State
|
|
||||||
if c.ContainerState.ExitCode != 0 {
|
|
||||||
return fmt.Errorf("%s", c.ContainerState.Error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) Delete(ctx context.Context) error {
|
func (c *Container) Delete(ctx context.Context) error {
|
||||||
slog.Info("Container.Delete()", "id", c.Id, "name", c.Name)
|
|
||||||
if stopErr := c.apiClient.ContainerStop(ctx, c.Id, container.StopOptions{}); stopErr != nil {
|
if stopErr := c.apiClient.ContainerStop(ctx, c.Id, container.StopOptions{}); stopErr != nil {
|
||||||
slog.Error("Container.Delete() - failed to stop: ", "Id", c.Id, "error", stopErr)
|
slog.Error("Container.Delete() - failed to stop: ", "Id", c.Id, "error", stopErr)
|
||||||
return stopErr
|
return stopErr
|
||||||
@ -566,7 +429,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.SetParsedURI(folio.URI(c.URI()).Parse()); err != nil {
|
if err = c.Common.SetURI(c.URI()); 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))
|
||||||
}
|
}
|
||||||
@ -604,64 +467,3 @@ func (c *Container) ResolveId(ctx context.Context) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
type WaitCondition func(state *types.ContainerState) bool
|
|
||||||
func (c *Container) wait(ctx context.Context, untilstate WaitCondition) error {
|
|
||||||
statusCh := make(chan bool)
|
|
||||||
timeoutCtx, cancel := context.WithTimeout(ctx, 60 * time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
if containerJSON, err := c.apiClient.ContainerInspect(ctx, c.Id); err == nil {
|
|
||||||
statusCh <- untilstate(containerJSON.State)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-timeoutCtx.Done():
|
|
||||||
return ErrContainerWaitTimeOut
|
|
||||||
case <-statusCh:
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Restart(ctx context.Context) (err error) {
|
|
||||||
if err = c.apiClient.ContainerRestart(ctx, c.Id, container.StopOptions{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = c.wait(ctx, func(state *types.ContainerState) bool {
|
|
||||||
return state.Running
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
waitTimeout := 60 * time.Second
|
|
||||||
interval := 2 * time.Second
|
|
||||||
deadline := time.Now().Add(waitTimeout)
|
|
||||||
|
|
||||||
for {
|
|
||||||
if time.Now().After(deadline) {
|
|
||||||
panic("")
|
|
||||||
}
|
|
||||||
if state.Running {
|
|
||||||
if state.Health != nil {
|
|
||||||
fmt.Println("Health status:", state.Health.Status)
|
|
||||||
if state.Health.Status == "healthy" {
|
|
||||||
fmt.Println("Container is healthy and running.")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Println("Container is running (no health check defined).")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(interval)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -93,6 +93,7 @@ type ContainerImage struct {
|
|||||||
outputWriter strings.Builder `json:"-" yaml:"-"`
|
outputWriter strings.Builder `json:"-" yaml:"-"`
|
||||||
|
|
||||||
apiClient ContainerImageClient
|
apiClient ContainerImageClient
|
||||||
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||||
contextDocument data.Document `json:"-" yaml:"-"`
|
contextDocument data.Document `json:"-" yaml:"-"`
|
||||||
ConverterTypes data.TypesRegistry[data.Converter] `json:"-" yaml:"-"`
|
ConverterTypes data.TypesRegistry[data.Converter] `json:"-" yaml:"-"`
|
||||||
|
|
||||||
@ -100,14 +101,11 @@ type ContainerImage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"container-image"}, func(u *url.URL) (c data.Resource) {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"container-image"}, func(u *url.URL) data.Resource {
|
||||||
c = NewContainerImage(nil)
|
c := NewContainerImage(nil)
|
||||||
if u != nil {
|
c.Name = ContainerImageNameFromURI(u)
|
||||||
if err := folio.CastParsedURI(u).ConstructResource(c); err != nil {
|
slog.Info("NewContainerImage", "container", c)
|
||||||
panic(err)
|
return c
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,24 +118,13 @@ func NewContainerImage(containerClientApi ContainerImageClient) *ContainerImage
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c := &ContainerImage{
|
return &ContainerImage{
|
||||||
Common: NewCommon(ContainerImageTypeName, true),
|
Common: &Common{ includeQueryParamsInURI: true, resourceType: ContainerImageTypeName },
|
||||||
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) {
|
||||||
@ -188,8 +175,8 @@ func (c *ContainerImage) RegistryAuth() (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ContainerImage) NormalizePath() error {
|
func (c *ContainerImage) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
return nil
|
c.Resources = resources
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ContainerImage) Clone() data.Resource {
|
func (c *ContainerImage) Clone() data.Resource {
|
||||||
@ -262,6 +249,24 @@ 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)
|
||||||
}
|
}
|
||||||
@ -784,6 +789,12 @@ 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" }
|
||||||
@ -810,3 +821,5 @@ func (c *ContainerImage) ResolveId(ctx context.Context) string {
|
|||||||
}
|
}
|
||||||
return c.Id
|
return c.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -214,8 +214,8 @@ attributes:
|
|||||||
_, readErr := contextFile.Resource().Read(context.Background())
|
_, readErr := contextFile.Resource().Read(context.Background())
|
||||||
assert.Nil(t, readErr)
|
assert.Nil(t, readErr)
|
||||||
|
|
||||||
c.Resources = folio.NewResourceMapper()
|
c.Resources = data.NewResourceMapper()
|
||||||
c.Resources.Set(folio.URI(contextDirUri), contextFile)
|
c.Resources.Set(contextDirUri, contextFile)
|
||||||
|
|
||||||
d, contextErr := c.ContextDocument()
|
d, contextErr := c.ContextDocument()
|
||||||
assert.Nil(t, contextErr)
|
assert.Nil(t, contextErr)
|
||||||
|
@ -15,6 +15,7 @@ _ "log/slog"
|
|||||||
"net/url"
|
"net/url"
|
||||||
_ "os"
|
_ "os"
|
||||||
_ "os/exec"
|
_ "os/exec"
|
||||||
|
"path/filepath"
|
||||||
_ "strings"
|
_ "strings"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
@ -38,32 +39,30 @@ 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:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"container-network"}, func(u *url.URL) (n data.Resource) {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"container-network"}, func(u *url.URL) data.Resource {
|
||||||
n = NewContainerNetwork(nil)
|
n := NewContainerNetwork(nil)
|
||||||
if u != nil {
|
n.Name = filepath.Join(u.Hostname(), u.Path)
|
||||||
if err := folio.CastParsedURI(u).ConstructResource(n); err != nil {
|
return n
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContainerNetwork(containerClientApi ContainerNetworkClient) (cn *ContainerNetwork) {
|
func NewContainerNetwork(containerClientApi ContainerNetworkClient) *ContainerNetwork {
|
||||||
var apiClient ContainerNetworkClient = containerClientApi
|
var apiClient ContainerNetworkClient = containerClientApi
|
||||||
if apiClient == nil {
|
if apiClient == nil {
|
||||||
var err error
|
var err error
|
||||||
@ -72,30 +71,22 @@ func NewContainerNetwork(containerClientApi ContainerNetworkClient) (cn *Contain
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cn = &ContainerNetwork{
|
return &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 {
|
func (n *ContainerNetwork) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
if u == nil {
|
n.Resources = resources
|
||||||
u = folio.URI(n.URI()).Parse()
|
|
||||||
}
|
|
||||||
return n.SetParsedURI(u)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ContainerNetwork) NormalizePath() error {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *ContainerNetwork) Clone() data.Resource {
|
func (n *ContainerNetwork) Clone() data.Resource {
|
||||||
return &ContainerNetwork {
|
return &ContainerNetwork {
|
||||||
Common: n.Common.Clone(),
|
Common: n.Common,
|
||||||
Id: n.Id,
|
Id: n.Id,
|
||||||
Name: n.Name,
|
Name: n.Name,
|
||||||
|
//State: n.State,
|
||||||
apiClient: n.apiClient,
|
apiClient: n.apiClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,6 +148,24 @@ 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)
|
||||||
}
|
}
|
||||||
|
@ -5,23 +5,20 @@ package resource
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"decl/tests/mocks"
|
"decl/tests/mocks"
|
||||||
"decl/internal/codec"
|
_ "encoding/json"
|
||||||
_ "encoding/json"
|
_ "fmt"
|
||||||
_ "fmt"
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"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"
|
||||||
"bytes"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewContainerResource(t *testing.T) {
|
func TestNewContainerResource(t *testing.T) {
|
||||||
@ -49,9 +46,6 @@ func TestReadContainer(t *testing.T) {
|
|||||||
ID: "123456789abc",
|
ID: "123456789abc",
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Image: "alpine",
|
Image: "alpine",
|
||||||
State: &types.ContainerState{
|
|
||||||
Status: "running",
|
|
||||||
},
|
|
||||||
}}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
||||||
@ -61,25 +55,21 @@ func TestReadContainer(t *testing.T) {
|
|||||||
go func() { resChan <- res }()
|
go func() { resChan <- res }()
|
||||||
return resChan, errChan
|
return resChan, errChan
|
||||||
},
|
},
|
||||||
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
|
|
||||||
return io.NopCloser(strings.NewReader("done.")), nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c := NewContainer(m)
|
c := NewContainer(m)
|
||||||
assert.NotNil(t, c)
|
assert.NotEqual(t, nil, c)
|
||||||
|
|
||||||
e := c.LoadDecl(decl)
|
e := c.LoadDecl(decl)
|
||||||
assert.Nil(t, e)
|
assert.Equal(t, nil, e)
|
||||||
assert.Equal(t, "testcontainer", c.Name)
|
assert.Equal(t, "testcontainer", c.Name)
|
||||||
|
|
||||||
resourceYaml, readContainerErr := c.Read(ctx)
|
resourceYaml, readContainerErr := c.Read(ctx)
|
||||||
assert.Nil(t, readContainerErr)
|
assert.Equal(t, nil, readContainerErr)
|
||||||
assert.Greater(t, len(resourceYaml), 0)
|
assert.Greater(t, len(resourceYaml), 0)
|
||||||
assert.Equal(t, "running", c.State)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDeleteContainer(t *testing.T) {
|
func TestCreateContainer(t *testing.T) {
|
||||||
m := &mocks.MockContainerClient{
|
m := &mocks.MockContainerClient{
|
||||||
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
|
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
|
||||||
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
||||||
@ -97,9 +87,6 @@ func TestCreateDeleteContainer(t *testing.T) {
|
|||||||
go func() { resChan <- res }()
|
go func() { resChan <- res }()
|
||||||
return resChan, errChan
|
return resChan, errChan
|
||||||
},
|
},
|
||||||
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
|
|
||||||
return io.NopCloser(strings.NewReader("done.")), nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decl := `
|
decl := `
|
||||||
@ -120,153 +107,3 @@ func TestCreateDeleteContainer(t *testing.T) {
|
|||||||
applyDeleteErr := c.Apply()
|
applyDeleteErr := c.Apply()
|
||||||
assert.Equal(t, nil, applyDeleteErr)
|
assert.Equal(t, nil, applyDeleteErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect the ContainerLog header for each entry
|
|
||||||
func TestContainerLogOutput(t *testing.T) {
|
|
||||||
logHeader := []byte{0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x05}
|
|
||||||
logHeader = append(logHeader, []byte(string("done."))...)
|
|
||||||
logs := bytes.NewReader(logHeader)
|
|
||||||
|
|
||||||
m := &mocks.MockContainerClient{
|
|
||||||
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
|
|
||||||
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
|
||||||
},
|
|
||||||
InjectContainerStop: func(context.Context, string, container.StopOptions) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
|
||||||
var res container.WaitResponse
|
|
||||||
resChan := make(chan container.WaitResponse)
|
|
||||||
errChan := make(chan error, 1)
|
|
||||||
go func() { resChan <- res }()
|
|
||||||
return resChan, errChan
|
|
||||||
},
|
|
||||||
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
|
|
||||||
return io.NopCloser(logs), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
c := NewContainer(m)
|
|
||||||
c.ReadFromContainer(context.Background())
|
|
||||||
assert.Equal(t, "done.", c.Stdout)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWaitContainer(t *testing.T) {
|
|
||||||
mockState := &types.ContainerState{
|
|
||||||
Status: "",
|
|
||||||
}
|
|
||||||
m := &mocks.MockContainerClient{
|
|
||||||
InjectContainerInspect: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
|
||||||
return types.ContainerJSON{
|
|
||||||
ContainerJSONBase: &types.ContainerJSONBase{
|
|
||||||
ID: "abcdef012",
|
|
||||||
Name: "testcontainer",
|
|
||||||
Image: "alpine",
|
|
||||||
State: mockState,
|
|
||||||
}}, nil
|
|
||||||
},
|
|
||||||
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
|
|
||||||
mockState.Status = "created"
|
|
||||||
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
|
||||||
},
|
|
||||||
InjectContainerStop: func(context.Context, string, container.StopOptions) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
|
||||||
var res container.WaitResponse
|
|
||||||
resChan := make(chan container.WaitResponse)
|
|
||||||
errChan := make(chan error, 1)
|
|
||||||
go func() { resChan <- res }()
|
|
||||||
return resChan, errChan
|
|
||||||
},
|
|
||||||
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
|
|
||||||
return io.NopCloser(strings.NewReader("done.")), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
c := NewContainer(m)
|
|
||||||
|
|
||||||
assert.Nil(t, c.LoadString(`
|
|
||||||
name: "testcontainer"
|
|
||||||
image: "alpine"
|
|
||||||
state: present
|
|
||||||
`, codec.FormatYaml))
|
|
||||||
|
|
||||||
assert.Nil(t, c.Apply())
|
|
||||||
|
|
||||||
assert.Equal(t, "testcontainer", c.Name)
|
|
||||||
assert.Nil(t, c.wait(context.Background(), func(state *types.ContainerState) bool {
|
|
||||||
return state.Status == "running"
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRestartContainer(t *testing.T) {
|
|
||||||
mockState := &types.ContainerState{
|
|
||||||
Status: "",
|
|
||||||
}
|
|
||||||
m := &mocks.MockContainerClient{
|
|
||||||
InjectContainerInspect: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
|
||||||
return types.ContainerJSON{
|
|
||||||
ContainerJSONBase: &types.ContainerJSONBase{
|
|
||||||
ID: "abcdef012",
|
|
||||||
Name: "testcontainer",
|
|
||||||
Image: "alpine",
|
|
||||||
State: mockState,
|
|
||||||
}}, nil
|
|
||||||
},
|
|
||||||
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
|
|
||||||
mockState.Status = "created"
|
|
||||||
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
|
||||||
},
|
|
||||||
InjectContainerStop: func(context.Context, string, container.StopOptions) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
InjectContainerRestart: func(ctx context.Context, containerID string, options container.StopOptions) error {
|
|
||||||
go func() {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
mockState.Status = "running"
|
|
||||||
mockState.Running = true
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
|
||||||
var res container.WaitResponse
|
|
||||||
resChan := make(chan container.WaitResponse)
|
|
||||||
errChan := make(chan error, 1)
|
|
||||||
go func() { resChan <- res }()
|
|
||||||
return resChan, errChan
|
|
||||||
},
|
|
||||||
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
|
|
||||||
return io.NopCloser(strings.NewReader("done.")), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
c := NewContainer(m)
|
|
||||||
|
|
||||||
assert.Nil(t, c.LoadString(`
|
|
||||||
name: "testcontainer"
|
|
||||||
image: "alpine"
|
|
||||||
state: present
|
|
||||||
`, codec.FormatYaml))
|
|
||||||
|
|
||||||
assert.Equal(t, "testcontainer", c.Name)
|
|
||||||
|
|
||||||
assert.Nil(t, c.Apply())
|
|
||||||
|
|
||||||
c.State = "present" // overwrite the state
|
|
||||||
c.StateMachine().Trigger("restart")
|
|
||||||
|
|
||||||
assert.Equal(t, "running", c.State)
|
|
||||||
|
|
||||||
assert.Nil(t, c.Apply())
|
|
||||||
}
|
|
||||||
|
@ -1,290 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package resource
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
_ "github.com/docker/docker/api/types/strslice"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
_ "log/slog"
|
|
||||||
"net/url"
|
|
||||||
_ "os"
|
|
||||||
_ "os/exec"
|
|
||||||
_ "strings"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
|
||||||
"decl/internal/codec"
|
|
||||||
"decl/internal/data"
|
|
||||||
"decl/internal/folio"
|
|
||||||
"log/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ContainerVolumeTypeName TypeName = "container-volume"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ContainerVolumeClient interface {
|
|
||||||
ContainerClient
|
|
||||||
VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error)
|
|
||||||
VolumeList(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error)
|
|
||||||
VolumeInspect(ctx context.Context, volumeID string) (volume.Volume, error)
|
|
||||||
VolumeRemove(ctx context.Context, volumeID string, force bool) (error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContainerVolume struct {
|
|
||||||
*Common `json:",inline" yaml:",inline"`
|
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
|
||||||
volume.Volume `json:",inline" yaml:",inline"`
|
|
||||||
|
|
||||||
apiClient ContainerVolumeClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"container-volume"}, func(u *url.URL) (n data.Resource) {
|
|
||||||
n = NewContainerVolume(nil)
|
|
||||||
if u != nil {
|
|
||||||
if err := folio.CastParsedURI(u).ConstructResource(n); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewContainerVolume(containerClientApi ContainerVolumeClient) (cn *ContainerVolume) {
|
|
||||||
var apiClient ContainerVolumeClient = containerClientApi
|
|
||||||
if apiClient == nil {
|
|
||||||
var err error
|
|
||||||
apiClient, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cn = &ContainerVolume{
|
|
||||||
apiClient: apiClient,
|
|
||||||
}
|
|
||||||
cn.Common = NewCommon(ContainerVolumeTypeName, true)
|
|
||||||
cn.Common.NormalizePath = cn.NormalizePath
|
|
||||||
return cn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Init(u data.URIParser) error {
|
|
||||||
if u == nil {
|
|
||||||
u = folio.URI(v.URI()).Parse()
|
|
||||||
}
|
|
||||||
return v.SetParsedURI(u)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) NormalizePath() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Clone() data.Resource {
|
|
||||||
return &ContainerVolume {
|
|
||||||
Common: v.Common.Clone(),
|
|
||||||
Volume: v.Volume,
|
|
||||||
apiClient: v.apiClient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) StateMachine() machine.Stater {
|
|
||||||
if v.stater == nil {
|
|
||||||
v.stater = StorageMachine(v)
|
|
||||||
}
|
|
||||||
return v.stater
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Notify(m *machine.EventMessage) {
|
|
||||||
ctx := context.Background()
|
|
||||||
slog.Info("Notify()", "ContainerVolume", v, "m", m)
|
|
||||||
switch m.On {
|
|
||||||
case machine.ENTERSTATEEVENT:
|
|
||||||
switch m.Dest {
|
|
||||||
case "start_read":
|
|
||||||
if _,readErr := v.Read(ctx); readErr == nil {
|
|
||||||
if triggerErr := v.StateMachine().Trigger("state_read"); triggerErr == nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
v.Common.State = "absent"
|
|
||||||
panic(triggerErr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
v.Common.State = "absent"
|
|
||||||
panic(readErr)
|
|
||||||
}
|
|
||||||
case "start_delete":
|
|
||||||
if deleteErr := v.Delete(ctx); deleteErr == nil {
|
|
||||||
if triggerErr := v.StateMachine().Trigger("deleted"); triggerErr == nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
v.Common.State = "present"
|
|
||||||
panic(triggerErr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
v.Common.State = "present"
|
|
||||||
panic(deleteErr)
|
|
||||||
}
|
|
||||||
case "start_create":
|
|
||||||
if e := v.Create(ctx); e == nil {
|
|
||||||
if triggerErr := v.StateMachine().Trigger("created"); triggerErr == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v.Common.State = "absent"
|
|
||||||
case "absent":
|
|
||||||
v.Common.State = "absent"
|
|
||||||
case "present", "created", "read":
|
|
||||||
v.Common.State = "present"
|
|
||||||
}
|
|
||||||
case machine.EXITSTATEEVENT:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) URI() string {
|
|
||||||
return fmt.Sprintf("container-volume://%s", v.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) JSON() ([]byte, error) {
|
|
||||||
return json.Marshal(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Validate() error {
|
|
||||||
return fmt.Errorf("failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Apply() error {
|
|
||||||
ctx := context.Background()
|
|
||||||
switch v.Common.State {
|
|
||||||
case "absent":
|
|
||||||
return v.Delete(ctx)
|
|
||||||
case "present":
|
|
||||||
return v.Create(ctx)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Load(docData []byte, f codec.Format) (err error) {
|
|
||||||
err = f.StringDecoder(string(docData)).Decode(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
|
|
||||||
err = f.Decoder(r).Decode(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) LoadString(docData string, f codec.Format) (err error) {
|
|
||||||
err = f.StringDecoder(docData).Decode(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ContainerVolume) LoadDecl(yamlResourceDeclaration string) error {
|
|
||||||
return n.LoadString(yamlResourceDeclaration, codec.FormatYaml)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Create(ctx context.Context) (err error) {
|
|
||||||
var spec volume.ClusterVolumeSpec
|
|
||||||
if v.ClusterVolume != nil {
|
|
||||||
spec = v.ClusterVolume.Spec
|
|
||||||
}
|
|
||||||
v.Volume, err = v.apiClient.VolumeCreate(ctx, volume.CreateOptions{
|
|
||||||
Name: v.Name,
|
|
||||||
Driver: v.Driver,
|
|
||||||
DriverOpts: v.Options,
|
|
||||||
Labels: v.Labels,
|
|
||||||
ClusterVolumeSpec: &spec,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Inspect(ctx context.Context, volumeID string) error {
|
|
||||||
volumeInspect, err := v.apiClient.VolumeInspect(ctx, volumeID)
|
|
||||||
if client.IsErrNotFound(err) {
|
|
||||||
v.Common.State = "absent"
|
|
||||||
} else {
|
|
||||||
v.Common.State = "present"
|
|
||||||
v.Volume = volumeInspect
|
|
||||||
if v.Name == "" {
|
|
||||||
if volumeInspect.Name[0] == '/' {
|
|
||||||
v.Name = volumeInspect.Name[1:]
|
|
||||||
} else {
|
|
||||||
v.Name = volumeInspect.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Read(ctx context.Context) ([]byte, error) {
|
|
||||||
var volumeID string
|
|
||||||
filterArgs := filters.NewArgs()
|
|
||||||
filterArgs.Add("name", v.Name)
|
|
||||||
volumes, err := v.apiClient.VolumeList(ctx, volume.ListOptions{
|
|
||||||
Filters: filterArgs,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%w: %s %s", err, v.Type(), v.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, vol := range volumes.Volumes {
|
|
||||||
if vol.Name == v.Name {
|
|
||||||
volumeID = vol.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if inspectErr := v.Inspect(ctx, volumeID); inspectErr != nil {
|
|
||||||
return nil, fmt.Errorf("%w: volume %s", inspectErr, volumeID)
|
|
||||||
}
|
|
||||||
slog.Info("Read() ", "type", v.Type(), "name", v.Name)
|
|
||||||
|
|
||||||
return yaml.Marshal(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Update(ctx context.Context) error {
|
|
||||||
return v.Create(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Delete(ctx context.Context) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Type() string { return "container-volume" }
|
|
||||||
|
|
||||||
func (v *ContainerVolume) ResolveId(ctx context.Context) string {
|
|
||||||
v.Inspect(ctx, v.Name)
|
|
||||||
return v.Name
|
|
||||||
/*
|
|
||||||
volumes, err := n.apiClient.VolumeInspect(ctx, volume.ListOptions{
|
|
||||||
|
|
||||||
filterArgs := filters.NewArgs()
|
|
||||||
filterArgs.Add("name", "/"+n.Name)
|
|
||||||
volumes, err := n.apiClient.VolumeList(ctx, volume.ListOptions{
|
|
||||||
All: true,
|
|
||||||
Filters: filterArgs,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("%w: %s %s", err, n.Type(), n.Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, volume := range volumes {
|
|
||||||
for _, containerName := range volume.Name {
|
|
||||||
if containerName == n.Name {
|
|
||||||
if n.Id == "" {
|
|
||||||
n.Id = container.ID
|
|
||||||
}
|
|
||||||
return container.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
*/
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package resource
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"decl/tests/mocks"
|
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewContainerVolumeResource(t *testing.T) {
|
|
||||||
c := NewContainerVolume(&mocks.MockContainerClient{})
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadContainerVolume(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
decl := `
|
|
||||||
name: "testcontainervolume"
|
|
||||||
state: present
|
|
||||||
`
|
|
||||||
m := &mocks.MockContainerClient{
|
|
||||||
InjectVolumeList: func(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error) {
|
|
||||||
return volume.ListResponse{
|
|
||||||
Volumes: []*volume.Volume{},
|
|
||||||
Warnings: []string{},
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
InjectVolumeInspect: func(ctx context.Context, volumeID string) (volume.Volume, error) {
|
|
||||||
return volume.Volume{
|
|
||||||
Name: "test",
|
|
||||||
Driver: "local",
|
|
||||||
Mountpoint: "/src",
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
v := NewContainerVolume(m)
|
|
||||||
assert.NotNil(t, v)
|
|
||||||
|
|
||||||
e := v.LoadDecl(decl)
|
|
||||||
assert.Nil(t, e)
|
|
||||||
assert.Equal(t, "testcontainervolume", v.Name)
|
|
||||||
|
|
||||||
resourceYaml, readContainerVolumeErr := v.Read(ctx)
|
|
||||||
assert.Equal(t, nil, readContainerVolumeErr)
|
|
||||||
assert.Greater(t, len(resourceYaml), 0)
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
// Container resource
|
|
||||||
package resource
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ContainerLogStreamType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
ContainerLogStreamStdin ContainerLogStreamType = 0x0
|
|
||||||
ContainerLogStreamStdout ContainerLogStreamType = 0x1
|
|
||||||
ContainerLogStreamStderr ContainerLogStreamType = 0x2
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrContainerLogInvalidStreamType error = errors.New("Invalid container log stream type")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s ContainerLogStreamType) Validate() error {
|
|
||||||
switch s {
|
|
||||||
case ContainerLogStreamStdin, ContainerLogStreamStdout, ContainerLogStreamStderr:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w: %d", ErrContainerLogInvalidStreamType, s)
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
||||||
|
|
||||||
package resource
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestContainerLogStreamType(t *testing.T) {
|
|
||||||
for _, v := range []struct{ expected error; value ContainerLogStreamType } {
|
|
||||||
{ expected: nil, value: 0x0 },
|
|
||||||
{ expected: nil, value: 0x1 },
|
|
||||||
{ expected: nil, value: 0x2 },
|
|
||||||
{ expected: ErrContainerLogInvalidStreamType, value: 0x3 },
|
|
||||||
{ expected: ErrContainerLogInvalidStreamType, value: 0x4 },
|
|
||||||
} {
|
|
||||||
assert.ErrorIs(t, v.value.Validate(), v.expected)
|
|
||||||
}
|
|
||||||
}
|
|
@ -34,25 +34,14 @@ type Exec struct {
|
|||||||
ReadTemplate *command.Command `yaml:"read,omitempty" json:"read,omitempty"`
|
ReadTemplate *command.Command `yaml:"read,omitempty" json:"read,omitempty"`
|
||||||
UpdateTemplate *command.Command `yaml:"update,omitempty" json:"update,omitempty"`
|
UpdateTemplate *command.Command `yaml:"update,omitempty" json:"update,omitempty"`
|
||||||
DeleteTemplate *command.Command `yaml:"delete,omitempty" json:"delete,omitempty"`
|
DeleteTemplate *command.Command `yaml:"delete,omitempty" json:"delete,omitempty"`
|
||||||
|
|
||||||
|
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
folio.DocumentRegistry.ResourceTypes.Register([]string{"exec"}, func(u *url.URL) (res data.Resource) {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"exec"}, func(u *url.URL) data.Resource {
|
||||||
x := NewExec()
|
x := NewExec()
|
||||||
res = x
|
return 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
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,9 +50,13 @@ func NewExec() *Exec {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Exec) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
|
x.Resources = resources
|
||||||
|
}
|
||||||
|
|
||||||
func (x *Exec) Clone() data.Resource {
|
func (x *Exec) Clone() data.Resource {
|
||||||
return &Exec {
|
return &Exec {
|
||||||
Common: x.Common.Clone(),
|
Common: x.Common,
|
||||||
Id: x.Id,
|
Id: x.Id,
|
||||||
CreateTemplate: x.CreateTemplate,
|
CreateTemplate: x.CreateTemplate,
|
||||||
ReadTemplate: x.ReadTemplate,
|
ReadTemplate: x.ReadTemplate,
|
||||||
@ -83,16 +76,13 @@ func (x *Exec) URI() string {
|
|||||||
return fmt.Sprintf("exec://%s", x.Id)
|
return fmt.Sprintf("exec://%s", x.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) Init(u data.URIParser) (err error) {
|
func (x *Exec) SetURI(uri string) (err error) {
|
||||||
if u == nil {
|
err = x.Common.SetURI(uri)
|
||||||
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 data.URIParser) (err error) {
|
func (x *Exec) SetParsedURI(uri *url.URL) (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,21 +4,20 @@
|
|||||||
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) {
|
||||||
@ -35,16 +34,7 @@ func TestExecApplyResourceTransformation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadExec(t *testing.T) {
|
func TestReadExec(t *testing.T) {
|
||||||
x := NewExec()
|
|
||||||
decl := `
|
|
||||||
read:
|
|
||||||
path: ls
|
|
||||||
args:
|
|
||||||
- -al
|
|
||||||
`
|
|
||||||
assert.Nil(t, x.LoadDecl(decl))
|
|
||||||
assert.Equal(t, "ls", x.ReadTemplate.Path)
|
|
||||||
assert.Equal(t, command.CommandArg("-al"), x.ReadTemplate.Args[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadExecError(t *testing.T) {
|
func TestReadExecError(t *testing.T) {
|
||||||
@ -65,10 +55,9 @@ 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.SetParsedURI(uri.Parse())
|
e := x.SetURI("exec://" + "12345_key")
|
||||||
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,6 +14,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@ -27,7 +28,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"embed"
|
"embed"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -56,14 +56,19 @@ 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) (res data.Resource) {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"file"}, func(u *url.URL) data.Resource {
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
slog.Info("FileFactory", "uri", u)
|
f.parsedURI = u
|
||||||
if u != nil {
|
//f.Uri.SetURL(u)
|
||||||
if err := folio.CastParsedURI(u).ConstructResource(f); err != nil {
|
f.Path = filepath.Join(u.Hostname(), u.Path)
|
||||||
panic(err)
|
f.exttype, f.fileext = f.Uri.Extension()
|
||||||
}
|
|
||||||
|
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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -78,12 +83,18 @@ The `SerializeContent` the flag allows forcing the content to be serialized in t
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
type File struct {
|
type File struct {
|
||||||
*Common `json:",inline" yaml:",inline"`
|
Uri folio.URI `json:"uri,omitempty" yaml:"uri,omitempty"`
|
||||||
|
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"`
|
||||||
@ -98,8 +109,11 @@ 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:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceFileInfo struct {
|
type ResourceFileInfo struct {
|
||||||
@ -109,49 +123,37 @@ 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{
|
f := &File{ normalizePath: false, Owner: currentUser.Username, Group: group.Name, Mode: "0644", FileType: RegularFile, SerializeContent: false }
|
||||||
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.PathNormalization(true)
|
f.normalizePath = 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) 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
|
||||||
}
|
}
|
||||||
if f.fileext == "" {
|
return f.exttype
|
||||||
return f.exttype
|
}
|
||||||
}
|
|
||||||
ext.WriteString(f.exttype)
|
func (f *File) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
ext.WriteRune('.')
|
f.Resources = resources
|
||||||
ext.WriteString(f.fileext)
|
|
||||||
return ext.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Clone() data.Resource {
|
func (f *File) Clone() data.Resource {
|
||||||
return &File {
|
return &File {
|
||||||
Common: f.Common.Clone(),
|
Uri: f.Uri,
|
||||||
|
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,
|
||||||
@ -163,6 +165,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,98 +182,57 @@ 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 {
|
} else {
|
||||||
_ = f.AddError(triggerErr)
|
if triggerErr := f.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
}
|
return
|
||||||
} else {
|
|
||||||
_ = f.AddError(readErr)
|
|
||||||
if f.IsResourceInconsistent() {
|
|
||||||
if triggerErr := f.StateMachine().Trigger("read-failed"); triggerErr == nil {
|
|
||||||
panic(readErr)
|
|
||||||
} else {
|
|
||||||
_ = f.AddError(triggerErr)
|
|
||||||
panic(fmt.Errorf("%w - %w", readErr, triggerErr))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = f.AddError(f.StateMachine().Trigger("notexists"))
|
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 {
|
||||||
|
f.State = "absent"
|
||||||
|
if ! errors.Is(readErr, ErrResourceStateAbsent) {
|
||||||
|
panic(readErr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "start_create":
|
case "start_create":
|
||||||
if createErr := f.Create(ctx); createErr == nil {
|
if e := f.Create(ctx); e == nil {
|
||||||
if triggerErr := f.StateMachine().Trigger("created"); triggerErr == nil {
|
if triggerErr := f.StateMachine().Trigger("created"); triggerErr == nil {
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
_ = f.AddError(triggerErr)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_ = f.AddError(createErr)
|
f.State = "absent"
|
||||||
if f.IsResourceInconsistent() {
|
panic(e)
|
||||||
if triggerErr := f.StateMachine().Trigger("create-failed"); triggerErr == nil {
|
|
||||||
panic(createErr)
|
|
||||||
} else {
|
|
||||||
_ = f.AddError(triggerErr)
|
|
||||||
panic(fmt.Errorf("%w - %w", createErr, triggerErr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = f.StateMachine().Trigger("notexists")
|
|
||||||
panic(createErr)
|
|
||||||
}
|
|
||||||
case "start_update":
|
|
||||||
if updateErr := f.Update(ctx); updateErr == nil {
|
|
||||||
if triggerErr := f.stater.Trigger("updated"); triggerErr == nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
_ = f.AddError(triggerErr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_ = f.AddError(updateErr)
|
|
||||||
if f.IsResourceInconsistent() {
|
|
||||||
if triggerErr := f.StateMachine().Trigger("update-failed"); triggerErr == nil {
|
|
||||||
panic(updateErr)
|
|
||||||
} else {
|
|
||||||
panic(fmt.Errorf("%w - %w", updateErr, triggerErr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = f.StateMachine().Trigger("notexists")
|
|
||||||
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.Common.State = "present"
|
f.State = "present"
|
||||||
panic(triggerErr)
|
panic(triggerErr)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_ = f.StateMachine().Trigger("exists")
|
f.State = "present"
|
||||||
panic(deleteErr)
|
panic(deleteErr)
|
||||||
}
|
}
|
||||||
case "inconsistent":
|
|
||||||
f.Common.State = "inconsistent"
|
|
||||||
case "absent":
|
case "absent":
|
||||||
f.Common.State = "absent"
|
f.State = "absent"
|
||||||
case "present", "created", "read":
|
case "present", "created", "read":
|
||||||
f.Common.State = "present"
|
f.State = "present"
|
||||||
}
|
}
|
||||||
case machine.EXITSTATEEVENT:
|
case machine.EXITSTATEEVENT:
|
||||||
switch m.Dest {
|
|
||||||
case "start_create":
|
|
||||||
slog.Info("File.Notify - EXITSTATE", "dest", m.Dest, "common.state", f.Common.State)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,8 +240,12 @@ 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.Common.Path
|
return f.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) SetFS(fsys fs.FS) {
|
func (f *File) SetFS(fsys fs.FS) {
|
||||||
@ -287,23 +253,63 @@ func (f *File) SetFS(fsys fs.FS) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) URI() string {
|
func (f *File) URI() string {
|
||||||
return fmt.Sprintf("file://%s", f.Common.Path)
|
return fmt.Sprintf("file://%s", f.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) RelativePath() string {
|
func (f *File) RelativePath() string {
|
||||||
return f.Common.Path[f.basePath:]
|
return f.Path[f.basePath:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) SetBasePath(index int) {
|
func (f *File) SetBasePath(index int) {
|
||||||
if index < len(f.Common.Path) {
|
if index < len(f.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)
|
||||||
}
|
}
|
||||||
@ -319,7 +325,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.Common.State {
|
switch f.State {
|
||||||
case "absent":
|
case "absent":
|
||||||
return f.Delete(ctx)
|
return f.Delete(ctx)
|
||||||
case "present":
|
case "present":
|
||||||
@ -361,19 +367,9 @@ 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.Common.Path
|
return f.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) NormalizePath() error {
|
|
||||||
if f.config != nil {
|
|
||||||
if prefixPath, configErr := f.config.GetValue("prefix"); configErr == nil {
|
|
||||||
f.Common.Path = filepath.Join(prefixPath.(string), f.Common.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f.Common.NormalizePath()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
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 {
|
||||||
@ -385,7 +381,6 @@ 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)
|
||||||
@ -398,7 +393,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.Common.Path)
|
info, err = fs.Stat(f.Filesystem, f.Path)
|
||||||
} else {
|
} else {
|
||||||
info, err = os.Lstat(f.absPath)
|
info, err = os.Lstat(f.absPath)
|
||||||
}
|
}
|
||||||
@ -458,12 +453,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.Common.Path)
|
linkErr := os.Symlink(f.Target, f.Path)
|
||||||
if linkErr != nil {
|
if linkErr != nil {
|
||||||
return linkErr
|
return linkErr
|
||||||
}
|
}
|
||||||
case DirectoryFile:
|
case DirectoryFile:
|
||||||
if mkdirErr := os.MkdirAll(f.Common.Path, mode); mkdirErr != nil {
|
if mkdirErr := os.MkdirAll(f.Path, mode); mkdirErr != nil {
|
||||||
return mkdirErr
|
return mkdirErr
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -491,8 +486,7 @@ func (f *File) Create(ctx context.Context) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
var createdFileWriter io.WriteCloser
|
var createdFileWriter io.WriteCloser
|
||||||
createdFile, fileErr := os.Create(f.Common.Path)
|
createdFile, fileErr := os.Create(f.Path)
|
||||||
slog.Info("File.Create(): os.Create()", "path", f.Common.Path, "error", fileErr)
|
|
||||||
if fileErr != nil {
|
if fileErr != nil {
|
||||||
return fileErr
|
return fileErr
|
||||||
}
|
}
|
||||||
@ -506,7 +500,6 @@ 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
|
||||||
}
|
}
|
||||||
@ -518,20 +511,16 @@ 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() {
|
||||||
slog.Info("File.Create(): Chtimes()", "path", f.Common.Path, "atime", f.Atime, "mtime", f.Mtime)
|
if chtimesErr := os.Chtimes(f.Path, f.Atime, f.Mtime); chtimesErr != nil {
|
||||||
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.Common.Path, uid, gid); chownErr != nil {
|
if chownErr := os.Chown(f.Path, uid, gid); chownErr != nil {
|
||||||
return chownErr
|
return chownErr
|
||||||
}
|
}
|
||||||
f.Common.State = "present"
|
f.State = "present"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -540,7 +529,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.Common.Path)
|
return os.Remove(f.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) UpdateContentAttributes() {
|
func (f *File) UpdateContentAttributes() {
|
||||||
@ -596,11 +585,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("File.ReadStat()", "filesystem", f.Filesystem, "path", f.Common.Path)
|
slog.Info("ReadStat()", "filesystem", f.Filesystem, "path", f.Path)
|
||||||
|
|
||||||
info, err = f.Stat()
|
info, err = f.Stat()
|
||||||
|
|
||||||
slog.Info("File.ReadStat()", "filesystem", f.Filesystem, "path", f.Common.Path, "info", info, "error", err)
|
slog.Info("ReadStat()", "filesystem", f.Filesystem, "path", f.Path, "info", info, "error", err)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_ = f.SetFileInfo(info)
|
_ = f.SetFileInfo(info)
|
||||||
@ -614,9 +603,9 @@ func (f *File) ReadStat() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
slog.Info("File.ReadStat()", "stat", info, "path", f.Common.Path)
|
slog.Info("ReadStat()", "stat", info, "path", f.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.Common.State = "absent"
|
f.State = "absent"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -624,29 +613,27 @@ 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.Common.Path, "fs", f.Filesystem)
|
slog.Info("open()", "file", f.Path, "fs", f.Filesystem)
|
||||||
if _, ok := f.Filesystem.(embed.FS); ok {
|
if _, ok := f.Filesystem.(embed.FS); ok {
|
||||||
file, err = f.Filesystem.Open(f.Common.Path)
|
file, err = f.Filesystem.Open(f.Path)
|
||||||
} else {
|
} else {
|
||||||
file, err = os.Open(f.Common.Path)
|
file, err = os.Open(f.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.Common.Path, "error", err)
|
slog.Info("open()", "file", f.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: %s", ErrResourceStateAbsent, statErr, f.Path)
|
return nil, fmt.Errorf("%w - %w", ErrResourceStateAbsent, statErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch f.FileType {
|
switch f.FileType {
|
||||||
@ -666,13 +653,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.Common.Path)
|
linkTarget, pathErr := os.Readlink(f.Path)
|
||||||
if pathErr != nil {
|
if pathErr != nil {
|
||||||
return nil, pathErr
|
return nil, pathErr
|
||||||
}
|
}
|
||||||
f.Target = linkTarget
|
f.Target = linkTarget
|
||||||
}
|
}
|
||||||
f.Common.State = "present"
|
f.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,7 +23,6 @@ _ "net/url"
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/folio"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,8 +37,7 @@ func TestNewFileNormalized(t *testing.T) {
|
|||||||
|
|
||||||
f := NewNormalizedFile()
|
f := NewNormalizedFile()
|
||||||
assert.NotNil(t, f)
|
assert.NotNil(t, f)
|
||||||
f.Path = indirectFile
|
assert.Nil(t, f.SetURI("file://" + 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)
|
||||||
@ -86,21 +84,13 @@ 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.NotNil(t, f)
|
assert.NotEqual(t, nil, 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)
|
||||||
@ -128,8 +118,7 @@ func TestUseConfig(t *testing.T) {
|
|||||||
return nil, data.ErrUnknownConfigurationKey
|
return nil, data.ErrUnknownConfigurationKey
|
||||||
}))
|
}))
|
||||||
|
|
||||||
uri := folio.URI(fmt.Sprintf("file://%s", file))
|
assert.Nil(t, f.SetURI(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())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,8 +263,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)
|
||||||
uri := folio.URI("file://" + file).Parse()
|
e := f.SetURI("file://" + file)
|
||||||
assert.Nil(t, f.Init(uri))
|
assert.Nil(t, e)
|
||||||
assert.Equal(t, "file", f.Type())
|
assert.Equal(t, "file", f.Type())
|
||||||
assert.Equal(t, file, f.Path)
|
assert.Equal(t, file, f.Path)
|
||||||
}
|
}
|
||||||
@ -313,7 +302,6 @@ 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")
|
||||||
|
|
||||||
@ -321,8 +309,8 @@ func TestFileReadStat(t *testing.T) {
|
|||||||
assert.NotNil(t, f)
|
assert.NotNil(t, f)
|
||||||
|
|
||||||
f.Path = linkTargetFile
|
f.Path = linkTargetFile
|
||||||
f.PathNormalization(true)
|
e := f.NormalizePath()
|
||||||
assert.Nil(t, f.Init(nil))
|
assert.Nil(t, e)
|
||||||
|
|
||||||
statErr := f.ReadStat()
|
statErr := f.ReadStat()
|
||||||
assert.Error(t, statErr)
|
assert.Error(t, statErr)
|
||||||
@ -336,9 +324,8 @@ 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
|
||||||
@ -353,9 +340,6 @@ 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)
|
||||||
@ -371,8 +355,6 @@ 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())
|
||||||
@ -396,45 +378,20 @@ 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.Common.Path, clone.Common.Path)
|
assert.Equal(t, f, clone)
|
||||||
assert.Equal(t, f.Common.absPath, clone.Common.absPath)
|
clone.Mtime = time.Time{}
|
||||||
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)
|
||||||
@ -442,8 +399,7 @@ func TestFileClone(t *testing.T) {
|
|||||||
_, cloneReadErr := clone.Read(ctx)
|
_, cloneReadErr := clone.Read(ctx)
|
||||||
assert.Nil(t, cloneReadErr)
|
assert.Nil(t, cloneReadErr)
|
||||||
|
|
||||||
slog.Info("TestFileClone - read mtime change", "orig", f.Mtime, "clone", clone.Mtime)
|
fmt.Printf("file %#v\nclone %#v\n", f, clone)
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,16 +412,12 @@ 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)
|
||||||
|
|
||||||
@ -582,8 +534,7 @@ 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) {
|
||||||
@ -642,8 +593,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)
|
||||||
uri := folio.URI("foo://" + file).Parse()
|
e := f.SetURI("foo://" + file)
|
||||||
e := f.Init(uri)
|
assert.NotNil(t, e)
|
||||||
assert.ErrorIs(t, e, ErrInvalidResourceURI)
|
assert.ErrorIs(t, e, ErrInvalidResourceURI)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,8 +602,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)
|
||||||
uri := folio.URI("file://" + file).Parse()
|
e := f.SetURI("file://" + file)
|
||||||
assert.Nil(t, f.Init(uri))
|
assert.Nil(t, e)
|
||||||
assert.Equal(t, "txt", f.ContentType())
|
assert.Equal(t, "txt", f.ContentType())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,9 +27,8 @@ type decodeGroup Group
|
|||||||
type GroupType string
|
type GroupType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
GroupTypeName TypeName = "group"
|
GroupTypeAddGroup = "addgroup"
|
||||||
GroupTypeAddGroup GroupType = "addgroup"
|
GroupTypeGroupAdd = "groupadd"
|
||||||
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")
|
||||||
@ -48,25 +47,28 @@ 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:"-"`
|
||||||
groupStatus *user.Group `json:"-" yaml:"-"`
|
State string `json:"state,omitempty" yaml:"state,omitempty"`
|
||||||
|
config data.ConfigurationValueGetter
|
||||||
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGroup() (g *Group) {
|
func NewGroup() *Group {
|
||||||
g = &Group{}
|
return &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) (group data.Resource) {
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"group"}, func(u *url.URL) data.Resource {
|
||||||
group = NewGroup()
|
group := NewGroup()
|
||||||
if u != nil {
|
group.Name = u.Hostname()
|
||||||
if err := folio.CastParsedURI(u).ConstructResource(group); err != nil {
|
group.GID = LookupGIDString(u.Hostname())
|
||||||
panic(err)
|
if _, addGroupPathErr := exec.LookPath("addgroup"); addGroupPathErr == nil {
|
||||||
}
|
group.GroupType = GroupTypeAddGroup
|
||||||
}
|
}
|
||||||
return
|
if _, pathErr := exec.LookPath("groupadd"); pathErr == nil {
|
||||||
|
group.GroupType = GroupTypeGroupAdd
|
||||||
|
}
|
||||||
|
group.CreateCommand, group.ReadCommand, group.UpdateCommand, group.DeleteCommand = group.GroupType.NewCRUD()
|
||||||
|
return group
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,32 +82,15 @@ func FindSystemGroupType() GroupType {
|
|||||||
return GroupTypeAddGroup
|
return GroupTypeAddGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Init(u data.URIParser) error {
|
func (g *Group) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
if u == nil {
|
g.Resources = resources
|
||||||
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) 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()
|
||||||
@ -124,62 +109,40 @@ 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.Common.State = "absent"
|
g.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)
|
||||||
}
|
}
|
||||||
@ -190,7 +153,7 @@ func (g *Group) Validate() error {
|
|||||||
|
|
||||||
func (g *Group) Apply() error {
|
func (g *Group) Apply() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
switch g.Common.State {
|
switch g.State {
|
||||||
case "present":
|
case "present":
|
||||||
_, NoGroupExists := LookupGID(g.Name)
|
_, NoGroupExists := LookupGID(g.Name)
|
||||||
if NoGroupExists != nil {
|
if NoGroupExists != nil {
|
||||||
@ -235,26 +198,10 @@ 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.Common.State = "absent"
|
g.State = "absent"
|
||||||
}
|
}
|
||||||
if yaml, yamlErr := yaml.Marshal(g); yamlErr != nil {
|
if yaml, yamlErr := yaml.Marshal(g); yamlErr != nil {
|
||||||
return yaml, yamlErr
|
return yaml, yamlErr
|
||||||
@ -263,9 +210,8 @@ func (g *Group) Read(ctx context.Context) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Update(ctx context.Context) (err error) {
|
func (g *Group) Update(ctx context.Context) (error) {
|
||||||
_, err = g.UpdateCommand.Execute(g)
|
return g.Create(ctx)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Delete(ctx context.Context) (error) {
|
func (g *Group) Delete(ctx context.Context) (error) {
|
||||||
@ -399,7 +345,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.Common.State = "absent"
|
g.State = "absent"
|
||||||
var readGroup *user.Group
|
var readGroup *user.Group
|
||||||
var e error
|
var e error
|
||||||
if g.Name != "" {
|
if g.Name != "" {
|
||||||
@ -414,7 +360,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.Common.State = "present"
|
g.State = "present"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
@ -423,17 +369,7 @@ func NewGroupReadCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewGroupUpdateCommand() *command.Command {
|
func NewGroupUpdateCommand() *command.Command {
|
||||||
c := command.NewCommand()
|
return nil
|
||||||
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 {
|
||||||
@ -480,7 +416,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.Common.State = "present"
|
g.State = "present"
|
||||||
g.GroupType = SystemGroupType
|
g.GroupType = SystemGroupType
|
||||||
lineIndex++
|
lineIndex++
|
||||||
}
|
}
|
||||||
|
@ -60,11 +60,16 @@ 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 {
|
|
||||||
if err := folio.CastParsedURI(u).ConstructResource(h); err != nil {
|
slog.Info("HTTP.Factory", "http", h, "url", u)
|
||||||
panic(err)
|
if err = h.SetParsedURI(u); err != nil {
|
||||||
}
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = h.Open(); err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
@ -91,39 +96,25 @@ type HTTP struct {
|
|||||||
LastModified time.Time `json:"lastmodified,omitempty" yaml:"lastmodified,omitempty"`
|
LastModified time.Time `json:"lastmodified,omitempty" yaml:"lastmodified,omitempty"`
|
||||||
Size int64 `yaml:"size,omitempty" json:"size,omitempty"`
|
Size int64 `yaml:"size,omitempty" json:"size,omitempty"`
|
||||||
SignatureValue string `yaml:"signature,omitempty" json:"signature,omitempty"`
|
SignatureValue string `yaml:"signature,omitempty" json:"signature,omitempty"`
|
||||||
|
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
||||||
reader *transport.Reader `yaml:"-" json:"-"`
|
reader *transport.Reader `yaml:"-" json:"-"`
|
||||||
writer *transport.ReadWriter `yaml:"-" json:"-"`
|
writer *transport.ReadWriter `yaml:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP() *HTTP {
|
func NewHTTP() *HTTP {
|
||||||
h := &HTTP{ client: &http.Client{} }
|
h := &HTTP{ client: &http.Client{}, Common: &Common{ includeQueryParamsInURI: true, resourceType: HTTPTypeName, SchemeCheck: func(scheme string) bool {
|
||||||
h.Common = NewCommon(HTTPTypeName, true)
|
switch scheme {
|
||||||
h.Common.SchemeCheck = h.SchemeCheck
|
case "http", "https":
|
||||||
h.Common.NormalizePath = h.NormalizePath
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} } }
|
||||||
slog.Info("NewHTTP()", "http", h)
|
slog.Info("NewHTTP()", "http", h)
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTP) SchemeCheck(scheme string) bool {
|
func (h *HTTP) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
switch scheme {
|
h.Resources = resources
|
||||||
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) Open() (err error) {
|
func (h *HTTP) Open() (err error) {
|
||||||
@ -228,9 +219,16 @@ func (h *HTTP) URI() string {
|
|||||||
return h.Endpoint.String()
|
return h.Endpoint.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTP) SetParsedURI(u data.URIParser) (err error) {
|
func (h *HTTP) SetURI(uri string) (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
|
||||||
}
|
}
|
||||||
@ -268,13 +266,11 @@ 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()
|
||||||
|
|
||||||
@ -336,14 +332,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.SetParsedURI(folio.URI(h.Endpoint).Parse())
|
err = h.Common.SetURI(string(h.Endpoint))
|
||||||
}
|
}
|
||||||
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.SetParsedURI(folio.URI(h.Endpoint).Parse())
|
err = h.Common.SetURI(string(h.Endpoint))
|
||||||
//err = h.setParsedURI(h.Endpoint)
|
//err = h.setParsedURI(h.Endpoint)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -351,7 +347,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.SetParsedURI(folio.URI(h.Endpoint).Parse())
|
err = h.Common.SetURI(string(h.Endpoint))
|
||||||
//err = h.setParsedURI(h.Endpoint)
|
//err = h.setParsedURI(h.Endpoint)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -362,7 +358,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.SetParsedURI(folio.URI(h.Endpoint).Parse())
|
_ = h.Common.SetURI(h.Endpoint.String())
|
||||||
slog.Info("HTTP.ResolveId()", "uri", h.Endpoint.String())
|
slog.Info("HTTP.ResolveId()", "uri", h.Endpoint.String())
|
||||||
return h.Endpoint.String()
|
return h.Endpoint.String()
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user