Compare commits

..

No commits in common. "main" and "v0.1.1" have entirely different histories.
main ... v0.1.1

309 changed files with 1589 additions and 27368 deletions

View File

@ -5,34 +5,16 @@ jobs:
lint:
name: golangci-lint
runs-on: ubuntu-latest
container:
env:
SSH_AUTH_SOCK: /tmp/ssh.sock
ENVIRONMENT: dev
volumes:
- "/etc/timezone:/etc/timezone:ro"
- "/etc/localtime:/etc/localtime:ro"
- "/etc/gitconfig:/etc/gitconfig:ro"
- "/${{ env.SSH_AUTH_SOCK }}:/tmp/ssh.sock"
- "/${{ vars.GITEA_WORKSPACE }}:/go/src"
- "/opt/gitea/act_runner/.ssh/known_hosts:/root/.ssh/known_hosts"
- "/opt/gitea/act_runner/.gitconfig:/root/.gitconfig:ro"
- "/opt/gitea/act_runner/.git-credentials:/root/.git-credentials:ro"
- "/etc/ssl/certs:/etc/ssl/certs:ro"
options: --cpus 20
steps:
- uses: actions/setup-go@v4
with:
go-version: "1.22.1"
go-version: "1.21.1"
cache: false
- name: Check out code
uses: actions/checkout@v3
- run: apt-get -y update && apt-get -y install libluajit-5.1-dev
- name: Get packages
run: go get ./...
- name: Lint
uses: golangci/golangci-lint-action@v3
with:

View File

@ -1,86 +1,21 @@
name: Releases
on:
create:
push:
tags:
- 'v*'
- '*'
jobs:
build-fedora:
build:
runs-on: ubuntu-latest
permissions:
contents: write
container:
image: fedora:latest
env:
GOPATH: /
SSH_AUTH_SOCK: /tmp/ssh.sock
ENVIRONMENT: dev
volumes:
- "/etc/timezone:/etc/timezone:ro"
- "/etc/localtime:/etc/localtime:ro"
- "/etc/gitconfig:/etc/gitconfig:ro"
- "/opt/gitea/act_runner/.ssh/known_hosts:/root/.ssh/known_hosts"
- "/opt/gitea/act_runner/.gitconfig:/root/.gitconfig:ro"
- "/opt/gitea/act_runner/.git-credentials:/root/.git-credentials:ro"
- "/etc/ssl/certs:/etc/ssl/certs:ro"
options: --cpus 4
steps:
- run: dnf install -y nodejs git make
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: dnf install -y dnf-plugins-core rpm-build rpmdevtools
- run: echo "$(pwd)/go/bin" >> $GITHUB_PATH
- run: dnf builddep -y build/jx.spec
- run: make build
- run: make rpm
- name: Archive binary
uses: actions/upload-artifact@v3
with:
name: "jx-fedora-40"
path: "jx"
- uses: actions/upload-artifact@v3
with:
name: "RPMS"
path: "/root/rpmbuild/**/*.rpm"
- run:
make build
- uses: ncipollo/release-action@v1
with:
artifacts: "jx,/root/rpmbuild/**/*.rpm"
- run: echo "This job's status is ${{ job.status }}."
build-ubuntu-focal:
runs-on: ubuntu-latest
permissions:
contents: write
container:
image: ubuntu:focal
env:
GOPATH: /
SSH_AUTH_SOCK: /tmp/ssh.sock
ENVIRONMENT: dev
DEBIAN_FRONTEND: noninteractive
TZ: America/Los_Angeles
volumes:
- "/etc/gitconfig:/etc/gitconfig:ro"
- "/opt/gitea/act_runner/.ssh/known_hosts:/root/.ssh/known_hosts"
- "/opt/gitea/act_runner/.gitconfig:/root/.gitconfig:ro"
- "/opt/gitea/act_runner/.git-credentials:/root/.git-credentials:ro"
options: --cpus 4
steps:
- run: apt-get -y update && apt-get install -y tzdata
- run: apt-get install -y nodejs git make
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: echo "$(pwd)/go/bin" >> $GITHUB_PATH
- run: make ubuntu-deps
- run: make build
- run: make deb
- uses: actions/upload-artifact@v3
with:
name: "jx-ubuntu-focal"
path: "jx"
- uses: ncipollo/release-action@v1
with:
artifacts: "jx"
artifacts: "decl"
bodyFile: "body.md"

View File

@ -9,20 +9,18 @@ jobs:
test:
runs-on: ubuntu-latest
container:
image: rosskeenhouse/build-golang:1.22.6-alpine
image: registry.cv.mazarbul.net/declarative/build-golang:1.21.1-alpine-x86_64
env:
GOPATH: /
SSH_AUTH_SOCK: /tmp/ssh.sock
ENVIRONMENT: dev
volumes:
- "/etc/timezone:/etc/timezone:ro"
- "/etc/localtime:/etc/localtime:ro"
- "/etc/gitconfig:/etc/gitconfig:ro"
- "/opt/gitea/act_runner/.ssh/known_hosts:/root/.ssh/known_hosts"
- "/opt/gitea/act_runner/.gitconfig:/root/.gitconfig:ro"
- "/opt/gitea/act_runner/.git-credentials:/root/.git-credentials:ro"
- "/etc/ssl/certs:/etc/ssl/certs:ro"
options: --cpus 10
- ${{ env.HOME }}/.ssh/known_hosts:/root/.ssh/known_hosts
- ${{ env.SSH_AUTH_SOCK }}:/tmp/ssh.sock
- /etc/gitconfig:/etc/gitconfig
- /etc/ssl/certs:/etc/ssl/certs
- ${{ vars.GITEA_WORKSPACE }}:/go/src
options: --cpus 1
steps:
- run: apk add --no-cache nodejs
- name: Check out repository code
@ -32,13 +30,6 @@ jobs:
- name: Run tests
run: |
go test -coverprofile=artifacts/coverage.profile ./...
- name: Run build
run: |
make build
- name: cli tests
run: |
go test
- run: echo "This job's status is ${{ job.status }}."
- run: echo "This job's status is ${{ job.status }}."
- name: coverage report
run: |
@ -46,11 +37,6 @@ jobs:
- name: Archive code coverage results
uses: actions/upload-artifact@v3
with:
name: "code-coverage-report"
path: "artifacts/code-coverage.html"
- name: Archive binary
uses: actions/upload-artifact@v3
with:
name: "jx-alpine"
path: "jx"
name: code-coverage-report
path: artifacts/code-coverage.html
- run: echo "This job's status is ${{ job.status }}."

View File

@ -1,47 +1,10 @@
export PATH := $(PATH):$(HOME)/go/bin
IMAGE?=fedora:latest
#LDFLAGS?=--ldflags '-extldflags "-static -ldl -lc -lm"' --ldflags="-X 'main.commit=$(shell git rev-parse HEAD)' -X 'main.version=$(shell git describe --tags)' -X 'main.date=$(shell date '+%Y-%m-%d %T.%s%z')'"
export CC?=muscl-gcc
LDFLAGS?=--ldflags '-extldflags "-static"' --ldflags="-X 'main.commit=$(shell git rev-parse HEAD)' -X 'main.version=$(shell git describe --tags)' -X 'main.date=$(shell date '+%Y-%m-%d %T.%s%z')'"
export CGO_ENABLED=1
VERSION?=$(shell git describe --tags | sed -e 's/^v//' -e 's/-/_/g')
.PHONY=jx-cli
LDFLAGS?=--ldflags '-extldflags "-static"'
export CGO_ENABLED=0
build: jx-cli
build: decl
jx-cli:
go build -o jx $(LDFLAGS) ./cmd/cli/main.go
decl:
go build -o decl $(LDFLAGS) ./cmd/cli/main.go
test: jx-cli
go test -coverprofile=artifacts/coverage.profile ./...
go tool cover -html=artifacts/coverage.profile -o artifacts/code-coverage.html
fedora-deps:
./jx apply build/rpm.jx.yaml
spectool -g -R build/jx.spec
curl -L -o - https://go.dev/dl/go1.22.3.linux-amd64.tar.gz | tar -zxvf -
rpm: fedora-deps
echo Building $(VERSION)
rpmbuild -ba --define='version $(VERSION)' build/jx.spec
ubuntu-deps:
apt-get update -y
apt-get install -y git make libluajit-5.1-dev iproute2 gcc curl
curl -L -o - https://go.dev/dl/go1.22.3.linux-amd64.tar.gz | tar -zxvf -
deb: ubuntu-deps
:
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
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
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:
go clean -modcache
rm jx
go-deps:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install golang.org/x/vuln/cmd/govulncheck@latest
lint:
golangci-lint run --verbose ./...
vulncheck:
govulncheck ./...
go vet ./...
test:
go test ./...

View File

@ -1,4 +1,4 @@
# jx
# decl
# Purpose
@ -8,109 +8,36 @@ These tools work with YAML descriptions of resources (E.g. files, users, contain
* Go >= 1.21.1
# Releases
**<span style="color:red">v0 releases are unstable and changes may be made to interfaces and specifications.</span>**
Use at your own risk.
# JX Documents
The JX YAML specification is a simple way to describe system resources. The two main components are `configurations` and `resources`.
## Configurations
```
configurations:
- name: myhttpconfig
values:
http_user: jex
http_password: sample
- name: myhttptoken
values:
authorization_token: abcde123456789
```
## Resources
```
resources:
- type: http
config: myhttptoken
transition: read
attributes:
endpoint: https://myserver/v1/api
```
# 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
make build-container
git clone https://gitea.rosskeen.house/Declarative/decl.git
make test
make build
```
# Command-line
# Update Resource state
`jx apply decl-runner.yaml`
Create the resources specified in a resource document HTTP endpoint.
`jx apply http://localhost/resources`
Convert a tar archive into resource definitions and apply them (extracts the contents of a tar).
`jx apply https://gitea.rosskeen.house/doublejynx/jx/archive/v0.2.1.tar.gz`
`decl -resource-file decl-runner.yaml`
# Read resource state
Read the state of an existing resource (URI) and generate a YAML representation of it.
`jx import -resource file://COPYRIGHT`
## Importing resources from different sources
JX supports importing resources data from various source types, among these are filesystem directories, tar archive contents, containers, iptables chains, installed packages, etc.
Import system packages using the debian package type, and output the resource documents in JSON format.
`jx import --output json:// package://?type=deb`
Import the contents of a tar archive into a resource document.
`jx import ./test.tgz`
Import resource documents from multiple sources.
`jx import repo/packages/build.jx.yaml ./gopkgs.tar.gz`
![Import Doc](md-images/jx-import.gif)
Read a resource document from an http endpoint.
`jx import http://localhost/resources`
# Diff resource documents
![Diff Resources](md-images/jx-diff.gif)
![Import Resource](md-images/import-resource.gif)
# Examples
Resources:
* [container](examples/container.jx.yaml) [schema](internal/resource/schemas/container.schema.json)
* [container-image](examples/container-image.jx.yaml) [schema](internal/resource/schemas/container-image.schema.json)
* [container-network](examples/container-network.jx.yaml) [schema](internal/resource/schemas/container-network.schema.json)
* [exec](examples/exec.jx.yaml) [schema](internal/resource/schemas/exec.schema.json)
* [file](examples/file.jx.yaml) [schema](internal/resource/schemas/file.schema.json)
* [group](examples/group.jx.yaml) [schema](internal/resource/schemas/group.schema.json)
* [http](examples/http.jx.yaml) [schema](internal/resource/schemas/http.schema.json)
* [iptable](examples/iptable.jx.yaml) [schema](internal/resource/schemas/iptable.schema.json)
* [network_route](examples/network_route.jx.yaml) [schema](internal/resource/schemas/network_route.schema.json)
* [package](examples/package.jx.yaml) [schema](internal/resource/schemas/package.schema.json)
* [user](examples/user.jx.yaml) [schema](internal/resource/schemas/user.schema.json)
* [file](examples/file.yaml) [schema](internal/resource/schemas/file.jsonschema)
* [user](examples/user.yaml) [schema](internal/resource/schemas/user.jsonschema)
* [package](examples/package.yaml) [schema](internal/resource/schemas/package.jsonschema)
* [container](examples/container.yaml) [schema](internal/resource/schemas/container.jsonschema)
* [network_route](examples/network_route.yaml) [schema](internal/resource/schemas/network_route.jsonschema)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,42 +0,0 @@
Name: jx
Version: %{version}
Release: %{!?rel:1}%{?dist}
Summary: Provision resources using a declarative YAML syntax.
License: https://gitea.rosskeen.house/doublejynx/jx/src/branch/main/LICENSE
URL: https://gitea.rosskeen.house/doublejynx/jx/
Source0: https://gitea.rosskeen.house/doublejynx/jx/archive/v0.2.2.tar.gz
BuildRequires: luajit-devel
BuildRequires: make
BuildRequires: golang >= 1.22.1
Requires: luajit
%description
%global debug_package %{nil}
%undefine _missing_build_ids_terminate_build
%global _missing_build_ids_terminate_build 0
%prep
%autosetup -n jx
%build
LDFLAGS=
%make_build
%install
mkdir -p %{buildroot}/usr/bin
cp jx %{buildroot}/usr/bin/jx
%files
%license LICENSE
#%doc add-docs-here
%{_bindir}/jx
%changelog
* Sun May 26 2024 Matthew Rich <matthewrich.conf@gmail.com> v0.2.2
-

View File

@ -8,278 +8,13 @@ import (
"os/exec"
"testing"
"errors"
"log/slog"
"net/http"
"net/http/httptest"
"fmt"
"decl/internal/tempdir"
"archive/tar"
"io"
"log"
"bytes"
)
var TempDir tempdir.Path = "testcli"
func TestMain(m *testing.M) {
err := TempDir.Create()
if err != nil || TempDir == "" {
slog.Error("Failed creating temp dir", "error", err)
}
rc := m.Run()
TempDir.Remove()
os.Exit(rc)
}
func TestCli(t *testing.T) {
if _, e := os.Stat("./jx"); errors.Is(e, os.ErrNotExist) {
if _, e := os.Stat("./decl"); errors.Is(e, os.ErrNotExist) {
t.Skip("cli not built")
}
yaml, cliErr := exec.Command("./jx", "import", "--resource", "file://COPYRIGHT").Output()
if cliErr != nil {
slog.Info("Debug CLI error", "error", cliErr, "stderr", cliErr.(*exec.ExitError).Stderr)
}
yaml, cliErr := exec.Command("./decl", "-import-resource", "file://decl").Output()
assert.Nil(t, cliErr)
assert.NotEqual(t, "", string(yaml))
assert.Greater(t, len(yaml), 0)
}
func TestCliHTTPSource(t *testing.T) {
if _, e := os.Stat("./jx"); errors.Is(e, os.ErrNotExist) {
t.Skip("cli not built")
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `
resources:
- type: file
attributes:
path: foo.txt
owner: nobody
group: nobody
mode: 0644
content: |
test file
content
state: present
`)
}))
defer ts.Close()
yaml, cliErr := exec.Command("./jx", "import", "--resource", ts.URL).Output()
if cliErr != nil {
slog.Info("Debug CLI error", "error", cliErr, "stderr", cliErr.(*exec.ExitError).Stderr)
}
assert.Nil(t, cliErr)
assert.NotEqual(t, "", string(yaml))
assert.Greater(t, len(yaml), 0)
}
func TestCliConfigSource(t *testing.T) {
if _, e := os.Stat("./jx"); errors.Is(e, os.ErrNotExist) {
t.Skip("cli not built")
}
configYaml := `
configurations:
- name: myhttpconnection
values:
http_user: foo
http_pass: bar
`
configPath := fmt.Sprintf("%s/testconfig.jx.yaml", TempDir)
f, err := os.Create(configPath)
assert.Nil(t, err)
defer f.Close()
_, writeErr := f.Write([]byte(configYaml))
assert.Nil(t, writeErr)
yaml, cliErr := exec.Command("./jx", "import", "--config", configPath, "--resource", "file://COPYRIGHT").Output()
if cliErr != nil {
slog.Info("Debug CLI error", "error", cliErr, "stderr", cliErr.(*exec.ExitError).Stderr)
}
assert.Nil(t, cliErr)
slog.Info("TestConfigSource", "yaml", yaml)
}
func TestCliConfigCommand(t *testing.T) {
if _, e := os.Stat("./jx"); errors.Is(e, os.ErrNotExist) {
t.Skip("cli not built")
}
yaml, cliErr := exec.Command("./jx", "config", "file://examples/config/file.jx.yaml").Output()
if cliErr != nil {
slog.Info("Debug CLI error", "error", cliErr, "stderr", cliErr.(*exec.ExitError).Stderr)
}
assert.Nil(t, cliErr)
slog.Info("TestConfigCommand", "yaml", yaml)
assert.NotEqual(t, "", string(yaml))
assert.Greater(t, len(yaml), 0)
}
func TestCliImportPackageCommand(t *testing.T) {
if _, e := os.Stat("./jx"); errors.Is(e, os.ErrNotExist) {
t.Skip("cli not built")
}
yaml, cliErr := exec.Command("./jx", "import", "package://").Output()
if cliErr != nil {
slog.Info("Debug CLI error", "error", cliErr, "stderr", cliErr.(*exec.ExitError).Stderr)
}
assert.Nil(t, cliErr)
assert.NotEqual(t, "", string(yaml))
assert.Greater(t, len(yaml), 0)
}
func TestCliExportTar(t *testing.T) {
if _, e := os.Stat("./jx"); errors.Is(e, os.ErrNotExist) {
t.Skip("cli not built")
}
assert.Nil(t, TempDir.Mkdir("tar", 0755))
assert.Nil(t, TempDir.CreateFile("tar/foo", "data"))
assert.Nil(t, TempDir.CreateFile("tar/bar", "data"))
cmdArgs := []string{"import", "--output", "tar://-", fmt.Sprintf("file://%s/tar", TempDir)}
slog.Info("TestCliExportTar()", "cmd", cmdArgs)
cmd := exec.Command("./jx", cmdArgs...)
slog.Info("TestCliExportTar()", "cmd", cmd)
stderr, errerr := cmd.StderrPipe()
assert.Nil(t, errerr)
stdout, outerr := cmd.StdoutPipe()
assert.Nil(t, outerr)
assert.Nil(t, cmd.Start())
errOutput, _ := io.ReadAll(stderr)
tarData, _ := io.ReadAll(stdout)
assert.Nil(t, cmd.Wait())
slog.Info("TestCliExportTar()", "stderr", errOutput)
assert.Greater(t, len(tarData), 0)
tr := tar.NewReader(bytes.NewBuffer(tarData))
files := []string{fmt.Sprintf("%s/tar/foo", TempDir), fmt.Sprintf("%s/tar/bar", TempDir)}
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
assert.Contains(t, files, hdr.Name)
contents, err := io.ReadAll(tr)
assert.Nil(t, err)
assert.Equal(t, []byte("data"), contents)
}
}
func TestResourcesRead(t *testing.T) {
if _, e := os.Stat("./jx"); errors.Is(e, os.ErrNotExist) {
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"))
resources := fmt.Sprintf(`
resources:
- type: file
transition: read
attributes:
path: %s
- type: user
transition: read
attributes:
name: nobody
- type: group
transition: read
attributes:
name: wheel
- type: container
transition: read
attributes:
name: builder
- type: container-network
transition: read
attributes:
name: default
- type: container-image
transition: read
attributes:
name: nginx:latest
- type: http
transition: read
attributes:
endpoint: %s/resource/user
- type: route
transition: read
attributes:
to: 0.0.0.0
gateway: 172.17.0.1
interface: eth0
proto: static
scope: global
rtid: all
routetype: local
metric: 100
`, TempDir.FilePath("testread"), server.URL)
assert.Nil(t, TempDir.CreateFile("resources.jx.yaml", resources))
yaml, cliErr := exec.Command("./jx", "apply", TempDir.FilePath("resources.jx.yaml")).Output()
if cliErr != nil {
slog.Info("Debug CLI error", "error", cliErr, "stderr", cliErr.(*exec.ExitError).Stderr)
}
assert.Nil(t, cliErr)
assert.NotEqual(t, "", string(yaml))
assert.Greater(t, len(yaml), 0)
}
func TestFailedResources(t *testing.T) {
if _, e := os.Stat("./jx"); errors.Is(e, os.ErrNotExist) {
t.Skip("cli not built")
}
os.Unsetenv("JX_DEBUG")
resources := `
resources:
- type: package
transition: create
attributes:
name: foobarbaz
`
assert.Nil(t, TempDir.CreateFile("err.jx.yaml", resources))
yaml, cliErr := exec.Command("./jx", "apply", TempDir.FilePath("err.jx.yaml")).Output()
if cliErr != nil {
slog.Info("Debug CLI error", "error", cliErr, "stderr", cliErr.(*exec.ExitError).Stderr)
}
assert.NotNil(t, cliErr)
assert.NotEqual(t, "", string(yaml))
assert.Contains(t, string(cliErr.(*exec.ExitError).Stderr), "Document errors: 1")
}

View File

@ -1,216 +1,72 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package main
import (
"context"
"decl/internal/data"
_ "decl/internal/config"
"decl/internal/folio"
_ "decl/internal/resource"
_ "decl/internal/fan"
"decl/internal/builtin"
_ "errors"
"os"
"flag"
"log"
"log/slog"
"fmt"
_ "gopkg.in/yaml.v3"
"io"
"log/slog"
"os"
"decl/internal/client"
"decl/internal/resource"
)
const (
FormatYaml = "yaml"
FormatJson = "json"
)
var (
version string
commit string
date string
)
var Client *client.App = client.NewClient()
var GlobalOformat *string
var GlobalOutput string
var GlobalQuiet *bool
var ImportMerge *bool
var ImportResource *string
var ApplyDelete *bool
var ConfigPath string
var ConfigDoc data.Document = folio.DocumentRegistry.NewDocument("")
var ctx context.Context = context.Background()
type RunCommand func(cmd *flag.FlagSet, output io.Writer) error
type SubCommand struct {
Name string
Run RunCommand
}
var jxSubCommands = []SubCommand{
{
Name: "diff",
Run: DiffSubCommand,
},
{
Name: "apply",
Run: ApplySubCommand,
},
{
Name: "import",
Run: ImportSubCommand,
},
{
Name: "config",
Run: ConfigSubCommand,
},
}
func VersionUsage() {
fmt.Println("jx")
fmt.Printf("version: %s\n", version)
fmt.Printf("commit: %s\n", commit)
fmt.Printf("date: %s\n", date)
}
func LoggerConfig() {
func main() {
fmt.Print("debugging\n")
var programLevel = new(slog.LevelVar)
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel}))
slog.SetDefault(logger)
if debugLogging, ok := os.LookupEnv("JX_DEBUG"); ok && debugLogging != "" {
if debugLogging,ok := os.LookupEnv("DECL_DEBUG"); ok && debugLogging != "" {
programLevel.Set(slog.LevelDebug)
} else {
programLevel.Set(slog.LevelError)
}
}
func ConfigSubCommand(cmd *flag.FlagSet, output io.Writer) (err error) {
if err = cmd.Parse(os.Args[2:]); err != nil {
return
}
if err = Client.SetOutput(GlobalOutput); err == nil {
if configErr := Client.SystemConfiguration(ConfigPath); configErr != nil {
slog.Info("Main.Import - SystemConfiguration", "config", ConfigPath, "error", configErr)
}
err = Client.ConfigCmd(cmd.Args(), true)
}
return
}
func ImportSubCommand(cmd *flag.FlagSet, output io.Writer) (err error) {
ImportResource = cmd.String("resource", "", "(uri) Add a resource to the document.")
ImportMerge = cmd.Bool("merge", false, "Merge resources into a single document.")
e := cmd.Parse(os.Args[2:])
if e != nil { // returns ErrHelp
return e
}
if err = Client.SetOutput(GlobalOutput); err == nil {
if configErr := Client.SystemConfiguration(ConfigPath); configErr != nil {
slog.Info("Main.Import - SystemConfiguration", "config", ConfigPath, "error", configErr)
}
err = Client.ImportCmd(ctx, cmd.Args(), *ImportResource, *GlobalQuiet, *ImportMerge)
}
return
}
func ApplySubCommand(cmd *flag.FlagSet, output io.Writer) (err error) {
ApplyDelete = cmd.Bool("delete", false, "Delete resources defined in the available documents.")
if e := cmd.Parse(os.Args[2:]); e != nil {
return e
}
if err = Client.SetOutput(GlobalOutput); err == nil {
if configErr := Client.SystemConfiguration(ConfigPath); configErr != nil {
slog.Info("Main.Import - SystemConfiguration", "config", ConfigPath, "error", configErr)
}
err = Client.ApplyCmd(ctx, cmd.Args(), *GlobalQuiet, *ApplyDelete)
}
return
}
func DiffSubCommand(cmd *flag.FlagSet, output io.Writer) (err error) {
if e := cmd.Parse(os.Args[2:]); e != nil {
return e
}
return Client.DiffCmd(cmd.Args())
}
func main() {
LoggerConfig()
if len(os.Args) < 2 {
fmt.Println("expected subcommands: diff, apply, import")
os.Exit(1)
}
DefaultConfigurations, configErr := builtin.BuiltInDocuments()
if configErr != nil {
slog.Warn("Failed loading default configuration", "error", configErr)
}
ConfigDoc.AppendConfigurations(DefaultConfigurations)
for _, subCmd := range jxSubCommands {
cmdFlagSet := flag.NewFlagSet(subCmd.Name, flag.ExitOnError)
cmdFlagSet.StringVar(&ConfigPath, "config", "/etc/jx/conf.d", "Config file path")
cmdFlagSet.StringVar(&ConfigPath, "c", "/etc/jx/conf.d", "Config file path")
GlobalOformat = cmdFlagSet.String("oformat", "yaml", "Output serialization format")
cmdFlagSet.StringVar(&GlobalOutput, "output", "-", "Output target (default stdout)")
cmdFlagSet.StringVar(&GlobalOutput, "o", "-", "Output target (default stdout)")
GlobalQuiet = cmdFlagSet.Bool("quiet", false, "Generate terse output.")
switch subCmd.Name {
case "diff":
cmdFlagSet.Usage = func() {
fmt.Println("jx diff source [source2]")
cmdFlagSet.PrintDefaults()
VersionUsage()
}
case "apply":
cmdFlagSet.Usage = func() {
fmt.Println("jx diff source [source2]")
cmdFlagSet.PrintDefaults()
VersionUsage()
}
case "import":
cmdFlagSet.Usage = func() {
fmt.Println("jx import source...")
cmdFlagSet.PrintDefaults()
VersionUsage()
}
case "config":
cmdFlagSet.Usage = func() {
fmt.Println("jx config source...")
cmdFlagSet.PrintDefaults()
VersionUsage()
}
}
slog.Info("CLI", "cmd", subCmd.Name)
if os.Args[1] == subCmd.Name {
if e := subCmd.Run(cmdFlagSet, os.Stdout); e != nil {
slog.Error("Failed running command", "command", os.Args[1], "error", e)
os.Exit(1)
}
return
}
}
flag.PrintDefaults()
VersionUsage()
os.Exit(1)
fmt.Print("debugging: flags\n")
file := flag.String("resource-file", "", "Resource file path")
resourceUri := flag.String("import-resource", "", "Add an existing resource")
flag.Parse()
var resourceFile *os.File
var inputFileErr error
slog.Info("args", "resource-file", *file, "import-resource", *resourceUri)
if *file == "-" {
if stdinInfo, stdinErr := os.Stdin.Stat(); stdinErr == nil {
if (stdinInfo.Mode() & os.ModeCharDevice) == 0 {
resourceFile = os.Stdin
}
} else {
inputFileErr = stdinErr
}
} else if *file != "" {
resourceFile,inputFileErr = os.Open(*file)
}
if inputFileErr != nil {
log.Fatal(inputFileErr)
}
d := resource.NewDocument()
slog.Info("loading resource document", "file", resourceFile)
if *file != "" {
if e := d.Load(resourceFile); e != nil {
log.Fatal(e)
}
if applyErr := d.Apply(); applyErr != nil {
log.Fatal(applyErr)
}
}
if *resourceUri != "" {
slog.Info("importing resource", "resource", *resourceUri)
if addResourceErr := d.AddResource(*resourceUri); addResourceErr != nil {
log.Fatal(addResourceErr)
}
}
if documentGenerateErr := d.Generate(os.Stdout); documentGenerateErr != nil {
log.Fatal(documentGenerateErr)
}
}

View File

@ -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())
*/
}

View File

@ -1,15 +0,0 @@
resources:
- type: pki
transition: create
config: myca
attributes:
privatekeyref: file://myca_privkey.pem
publickeyref: file://myca_pubkey.pem
certificateref: file://myca_cert.pem
- type: pki
transition: update
attributes:
signedbyref: pki://myca_privkey.pem
privatekeyref: file://mycert_key.pem
publickeyref: file://mycert_pubkey.pem
certificateref: file://mycert.pem

View File

@ -1,43 +0,0 @@
configurations:
- name: myca
type: certificate
values:
certtemplate:
serialnumber: 2024
subject:
organization:
- RKH
country:
- US
province:
- CA
locality:
- San Francisco
streetaddress:
- 0 cert st
postalcode:
- 94101
notbefore: 2024-07-10
notafter: 2025-07-10
basicconstraintsvalid: true
isca: true
- name: mycert
type: certificate
values:
certtemplate:
serialnumber: 2025
subject:
organization:
- RKH
country:
- US
province:
- CA
locality:
- San Francisco
streetaddress:
- 0 cert st
postalcode:
- 94101
notbefore: 2024-07-10
notafter: 2025-07-10

View File

@ -1,4 +0,0 @@
configurations:
- name: myfiles
values:
prefix: /home/testuser

View File

@ -1,5 +0,0 @@
resources:
- type: container-image
transition: read
attributes:
name: nginx:latest

View File

@ -1,5 +0,0 @@
resources:
- type: container-image
transition: read
attributes:
name: "fedora:latest"

View File

@ -1,8 +1,8 @@
resources:
- type: file
transition: create
attributes:
path: /tmp/foo.txt
owner: nobody
group: nobody
mode: 0644
state: present

View File

@ -1,6 +0,0 @@
resources:
- type: file
transition: create
attributes:
path: go1.22.5.linux-amd64.tar.gz
sourceref: https://go.dev/dl/go1.22.5.linux-amd64.tar.gz

View File

@ -1,12 +0,0 @@
resources:
- type: file
transition: create
attributes:
path: golangci-lint-1.55.2-linux-amd64.deb
sourceref: https://github.com/golangci/golangci-lint/releases/download/v1.55.2/golangci-lint-1.55.2-linux-amd64.deb
- type: package
transition: create
attributes:
name: golangci-lint
source: golangci-lint-1.55.2-linux-amd64.deb
type: deb

View File

@ -1,6 +0,0 @@
resources:
- type: group
transition: create
attributes:
name: "testgroup"
gid: "12001"

View File

@ -1,3 +0,0 @@
# Import the built-in install document which install the jx binary.
imports:
- file://documents/install.jx.yaml

View File

@ -1,11 +0,0 @@
resources:
- type: iptable
transition: create
attributes:
id: 1
table: filter
chain: INPUT
jump: LIBVIRT_INP
state: present
resourcetype: rule

View File

@ -1,9 +0,0 @@
resources:
- type: package
transition: create
attributes:
name: zip
version: 3.0-12build2
type: apt
state: present

View File

@ -1,7 +1,7 @@
resources:
- type: user
transition: create
attributes:
name: "testuser"
uid: "12001"
home: "/home/testuser"
state: present

49
go.mod
View File

@ -1,56 +1,31 @@
module decl
go 1.22.5
go 1.21.1
require github.com/stretchr/testify v1.8.4
require (
// gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240513200425-f413d8adf7b3
gitea.rosskeen.house/rosskeen.house/machine v0.0.0-20240520193117-1835255b6d02
github.com/docker/docker v27.0.3+incompatible
github.com/docker/go-connections v0.5.0
github.com/opencontainers/image-spec v1.1.0
github.com/sters/yaml-diff v1.3.2
github.com/stretchr/testify v1.9.0
github.com/xeipuuv/gojsonschema v1.2.0
gopkg.in/yaml.v3 v3.0.1
)
require google.golang.org/protobuf v1.33.0
require (
gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240924031921-4d00743b53e1 // indirect
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/docker v25.0.5+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-yaml v1.11.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.25.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 // indirect
go.opentelemetry.io/otel/metric v1.25.0 // indirect
go.opentelemetry.io/otel/sdk v1.25.0 // indirect
go.opentelemetry.io/otel/trace v1.25.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
gotest.tools/v3 v3.5.1 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/sys v0.1.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

143
go.sum
View File

@ -1,35 +1,18 @@
gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240513200425-f413d8adf7b3 h1:ge74Hmzxp+bqVwSK9hOOBlZB9KeL3xuwMIXAYLPHBxA=
gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240513200425-f413d8adf7b3/go.mod h1:9sKIXsGDcf1uBnHhY29wi38Vll8dpVNUOxkXphN2KEk=
gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240924031921-4d00743b53e1 h1:UT79l0TvkYjlAbJrsFIm6R0tL+Rl/814ThKbjOgrTPo=
gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240924031921-4d00743b53e1/go.mod h1:9sKIXsGDcf1uBnHhY29wi38Vll8dpVNUOxkXphN2KEk=
gitea.rosskeen.house/rosskeen.house/machine v0.0.0-20240520193117-1835255b6d02 h1:FLRmUvu0mz8Ac+/VZf/P4yuv2e6++SSkKOcEIHSlpAI=
gitea.rosskeen.house/rosskeen.house/machine v0.0.0-20240520193117-1835255b6d02/go.mod h1:5J2OFjFIBaCfsjcC9kSyycbIL8g/qAJH2A8BnbIig+Y=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE=
github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ=
github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE=
github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@ -37,60 +20,28 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I=
github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sters/yaml-diff v1.3.2 h1:99Ke50QYFQYZjKMOiePxwyuQ+WeCvNy6cRooqdLs/ZE=
github.com/sters/yaml-diff v1.3.2/go.mod h1:86usbNZiUqke5wYjMxDVEjmvGjmY2FkMwOwe0A5zf68=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
@ -99,106 +50,44 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k=
go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 h1:dT33yIHtmsqpixFsSQPwNeY5drM9wTcoL8h0FWF4oGM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0/go.mod h1:h95q0LBGh7hlAC08X2DhSeyIG02YQ0UyioTCVAqRPmc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 h1:Mbi5PKN7u322woPa85d7ebZ+SOvEoPvoiBu+ryHWgfA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0/go.mod h1:e7ciERRhZaOZXVjx5MiL8TK5+Xv7G5Gv5PA2ZDEJdL8=
go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA=
go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s=
go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo=
go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw=
go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM=
go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I=
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8=
google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=

View File

@ -1,69 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package builtin
import (
_ "context"
_ "encoding/json"
"fmt"
_ "gopkg.in/yaml.v3"
_ "net/url"
_ "regexp"
_ "strings"
_ "os"
_ "io"
_ "compress/gzip"
_ "archive/tar"
_ "errors"
_ "path/filepath"
_ "decl/internal/codec"
"decl/internal/data"
"decl/internal/fs"
"decl/internal/folio"
_ "decl/internal/resource"
_ "decl/internal/config"
_ "decl/internal/fan"
"embed"
"log/slog"
)
//go:embed documents/*.jx.yaml
var documentFiles embed.FS
func Load(uri folio.URI) (documents []data.Document, err error) {
var extractor data.Converter
var sourceResource data.Resource
if extractor, err = folio.DocumentRegistry.ConverterTypes.New(string(uri)); err == nil {
slog.Info("builtin.Load() extractor", "uri", uri, "error", err)
targetDeclaration := folio.NewDeclaration()
if err = targetDeclaration.NewResource((*string)(&uri)); err == nil {
slog.Info("builtin.Load() extract many", "resource", sourceResource, "error", err, "uri", uri, "extractor", extractor)
sourceResource = targetDeclaration.Attributes
sourceResource.(data.FileResource).SetFS(documentFiles)
documents, err = extractor.(data.ManyExtractor).ExtractMany(sourceResource, nil)
slog.Info("builtin.Load() extract many", "resource", sourceResource, "error", err)
}
}
return
}
func BuiltInDocuments() (documents []data.Document, err error) {
docFs := fs.NewWalkDir(documentFiles, "", func(fsys fs.FS, path string, file fs.DirEntry) (walkErr error) {
u := folio.URI(fmt.Sprintf("file://%s", path))
slog.Info("BuiltInDocuments()", "file", u)
if ! file.IsDir() {
if loadDocs, loadErr := Load(u); loadErr == nil {
documents = append(documents, loadDocs...)
} else {
err = loadErr
}
}
return
})
docFs.Walk(nil)
return documents, err
}

View File

@ -1,59 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package builtin
import (
"github.com/stretchr/testify/assert"
"log/slog"
"os"
"testing"
"decl/internal/tempdir"
"decl/internal/folio"
"errors"
)
var TempDir tempdir.Path = "testbuiltin"
func TestMain(m *testing.M) {
err := TempDir.Create()
if err != nil || TempDir == "" {
slog.Error("Failed creating temp dir", "error", err)
}
rc := m.Run()
TempDir.Remove()
os.Exit(rc)
}
func TestBuiltInLoad(t *testing.T) {
docs, err := Load("file://documents/facter.jx.yaml")
if ! errors.Is(err, os.ErrNotExist) {
assert.Nil(t, err)
assert.Greater(t, len(docs), 0)
}
}
func TestBuiltInDocuments(t *testing.T) {
docs, err := BuiltInDocuments()
assert.Greater(t, len(docs), 0)
if ! errors.Is(err, os.ErrNotExist) {
assert.Nil(t, err)
config, ok := folio.DocumentRegistry.GetDocument("file://documents/facter.jx.yaml")
assert.True(t, ok)
slog.Info("TestBuiltInDocuments()", "docuemnt", config)
assert.True(t, config.HasConfig("facts"))
c := config.GetConfig("facts")
v, e := c.GetValue("virtual")
assert.Nil(t, e)
assert.Equal(t, "physical", v)
}
systemConfig, systemExists := folio.DocumentRegistry.GetDocument("file://documents/system.jx.yaml")
assert.True(t, systemExists)
assert.True(t, systemConfig.HasConfig("system"))
}

View File

@ -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

View File

@ -1,9 +0,0 @@
configurations:
- name: facts
type: exec
values:
path: /usr/bin/facter
args:
- "-j"
format: "json"

View File

@ -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

View File

@ -1,4 +0,0 @@
configurations:
- name: system
type: system
values: {}

View File

@ -1,392 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package client
import (
"decl/internal/data"
"decl/internal/folio"
_ "decl/internal/fan"
_ "decl/internal/config"
_ "decl/internal/resource"
"decl/internal/fs"
"decl/internal/builtin"
"errors"
"fmt"
"context"
"log/slog"
"os"
)
var (
ErrFailedResources error = errors.New("Failed Resources")
ErrFailedDocuments error = errors.New("Document errors")
)
type App struct {
Target folio.URI
ImportedMap map[folio.URI]data.Document
Documents []data.Document
emitter data.Converter
merged data.Document
Config data.Document
}
func NewClient() *App {
a := &App{ ImportedMap: make(map[folio.URI]data.Document), Documents: make([]data.Document, 0, 100) }
return a
}
// Load compiled-in config documents.
func (a *App) BuiltInConfiguration() (err error) {
var defaultConfigurations []data.Document
if defaultConfigurations, err = builtin.BuiltInDocuments(); len(defaultConfigurations) > 0 {
slog.Info("Client.BuiltInConfiguration()", "documents", defaultConfigurations, "error", err)
a.Config.AppendConfigurations(defaultConfigurations)
}
return
}
// Load config documents from default system config path. Ignore if missing.
func (a *App) SystemConfiguration(configPath string) (err error) {
var extractor data.Converter
var sourceResource data.Resource
if a.Config == nil {
a.Config = folio.DocumentRegistry.NewDocument("file:///etc/jx/runtimeconfig.jx.yaml")
}
if configPath != "" {
//configURI := folio.URI(configPath)
var loaded []data.Document
docFs := fs.NewWalkDir(os.DirFS(configPath), configPath, func(fsys fs.FS, path string, file fs.DirEntry) (loadErr error) {
u := folio.URI(fmt.Sprintf("file://%s", path))
if ! file.IsDir() {
slog.Info("Client.SystemConfiguration()", "uri", u)
if extractor, loadErr = folio.DocumentRegistry.ConverterTypes.New(string(u)); loadErr == nil {
if sourceResource, loadErr = u.NewResource(nil); loadErr == nil {
if loaded, loadErr = extractor.(data.ManyExtractor).ExtractMany(sourceResource, nil); loadErr == nil {
a.Config.AppendConfigurations(loaded)
}
}
}
}
return
})
err = docFs.Walk(nil)
}
return
}
func (a *App) MergeDocuments() {
a.merged = folio.DocumentRegistry.NewDocument("file://-")
for _, d := range a.Documents {
for _, declaration := range d.(*folio.Document).ResourceDeclarations {
a.merged.AddDeclaration((data.Declaration)(declaration))
}
}
}
func (a *App) SetOutput(uri string) (err error) {
if uri == "-" {
uri = "jx://-"
}
a.Target = folio.URI(uri)
if a.emitter, err = folio.DocumentRegistry.ConverterTypes.New(uri); err != nil {
return fmt.Errorf("Failed opening target: %s, %w", uri, err)
}
slog.Info("Client.SetOutput()", "uri", uri, "emitter", a.emitter)
return
}
// Each document has an `imports` keyword which can be used to load dependencies
func (a *App) LoadDocumentImports() error {
slog.Info("Client.LoadDocumentImports()", "documents", a.Documents)
for i, d := range a.Documents {
importedDocs := d.ImportedDocuments()
slog.Info("Client.LoadDocumentImports()", "imported", importedDocs)
for _, importedDocument := range importedDocs {
docURI := folio.URI(importedDocument.GetURI())
if _, ok := a.ImportedMap[docURI]; !ok {
a.ImportedMap[docURI] = importedDocument
a.Documents = append(a.Documents, nil)
copy(a.Documents[i+1:], a.Documents[i:])
a.Documents[i] = importedDocument
/*
if _, outputErr := a.emitter.Emit(importedDocument, nil); outputErr != nil {
return outputErr
}
*/
}
}
}
return nil
}
func (a *App) ImportResource(ctx context.Context, uri string) (err error) {
if len(a.Documents) < 1 {
a.Documents = append(a.Documents, folio.DocumentRegistry.NewDocument(""))
}
resourceURI := folio.URI(uri)
u := resourceURI.Parse().URL()
if u == nil {
return fmt.Errorf("Failed adding resource: %s", uri)
}
if u.Scheme == "" {
u.Scheme = "file"
}
for _, d := range a.Documents {
if newResource, newResourceErr := d.NewResource(uri); newResourceErr == nil {
if _, err = newResource.Read(ctx); err != nil {
return
}
} else {
return newResourceErr
}
}
return
}
func (a *App) ImportSource(uri string) (loadedDocuments []data.Document, err error) {
if source := folio.URI(uri).Parse().URL(); source != nil {
if source.Scheme == "" {
source.Scheme = "file"
}
slog.Info("Client.ImportSource()", "uri", uri, "source", source, "error", err)
if loadedDocuments, err = folio.DocumentRegistry.LoadFromParsedURI(source); err == nil && loadedDocuments != nil {
a.Documents = append(a.Documents, loadedDocuments...)
}
} else {
err = folio.ErrInvalidURI
}
slog.Info("Client.ImportSource()", "uri", uri, "error", err)
return
}
func (a *App) Import(docs []string) (err error) {
for _, source := range docs {
if _, err = a.ImportSource(source); err != nil {
return
}
}
return
}
func (a *App) Apply(ctx context.Context, deleteResources bool) (err error) {
var errorsCount int = 0
for _, d := range a.Documents {
d.SetConfig(a.Config)
var overrideState string = ""
if deleteResources {
overrideState = "delete"
}
d.ResolveIds(ctx)
_ = d.Apply("stat")
if ! d.CheckConstraints() {
slog.Info("Client.Apply() document constraints failed", "requires", d)
d.AddError(fmt.Errorf("%w: %s", folio.ErrConstraintFailure, d.GetURI()))
errorsCount++
continue
}
slog.Info("Client.Apply()", "uri", d.GetURI(), "document", d, "state", overrideState, "error", err)
if e := d.(*folio.Document).Apply(overrideState); e != nil {
slog.Info("Client.Apply() error", "error", e)
return e
}
if d.Failures() > 0 {
d.AddError(fmt.Errorf("%w: %d, %w", ErrFailedResources, d.Failures(), err))
errorsCount++
}
}
if errorsCount > 0 {
return fmt.Errorf("%w: %d", ErrFailedDocuments, errorsCount)
}
return
}
func (a *App) ImportCmd(ctx context.Context, docs []string, resourceURI string, quiet bool, merge bool) (err error) {
defer a.Close()
if err = a.Import(docs); err != nil {
return
}
if err = a.LoadDocumentImports(); err != nil {
return
}
if len(resourceURI) > 0 {
if err = a.ImportResource(ctx, resourceURI); err != nil {
return
}
}
if quiet {
err = a.Quiet()
} else {
if merge {
a.MergeDocuments()
}
err = a.Emit()
if err != nil {
return
}
}
return
}
func (a *App) ApplyCmd(ctx context.Context, docs []string, quiet bool, deleteResources bool) (err error) {
defer a.Close()
var failedResources error
if err = a.Import(docs); err != nil {
return
}
if err = a.LoadDocumentImports(); err != nil {
return
}
if failedResources = a.Apply(ctx, deleteResources); failedResources != nil {
slog.Info("Client.ApplyCmd()", "client", a, "error", failedResources)
if ! errors.Is(failedResources, ErrFailedResources) && ! errors.Is(failedResources, ErrFailedDocuments) {
return failedResources
}
}
if quiet {
err = a.Quiet()
} else {
err = a.Emit()
}
if failedResources != nil {
if err != nil {
return fmt.Errorf("%w %w", failedResources, err)
} else {
return failedResources
}
}
return
}
func (a *App) Diff(left []data.Document, right []data.Document) (err error) {
output := os.Stdout
slog.Info("jx diff ", "right", right, "left", left)
index := 0
for {
if index >= len(right) && index >= len(left) {
break
}
if index >= len(right) {
if _, err = left[index].Diff(folio.DocumentRegistry.NewDocument(""), output); err != nil {
return
}
index++
continue
}
if index >= len(left) {
if _, err = folio.DocumentRegistry.NewDocument("").Diff(right[index], output); err != nil {
return
}
index++
continue
}
if _, err = left[index].Diff(right[index], output); err != nil {
return
}
index++
}
return
}
func (a *App) DiffCmd(docs []string) (err error) {
output := os.Stdout
var leftDocuments, rightDocuments []data.Document
var rightSource folio.URI
//leftSource := folio.URI(docs[0])
if len(docs) > 1 {
rightSource = folio.URI(docs[1])
}
if leftDocuments, err = a.ImportSource(docs[0]); err == nil {
if rightSource.IsEmpty() {
for _, doc := range leftDocuments {
_, err = doc.DiffState(output)
}
} else {
if rightDocuments, err = a.ImportSource(docs[1]); err == nil {
err = a.Diff(leftDocuments, rightDocuments)
}
}
}
return err
}
func (a *App) ConfigCmd(docs []string, includeSystemConfig bool) (err error) {
defer a.Close()
if err = a.BuiltInConfiguration(); err != nil {
slog.Warn("BuiltInConfiguration()", "error", err)
}
if err = a.Import(docs); err != nil {
return
}
if err = a.LoadDocumentImports(); err != nil {
return
}
if includeSystemConfig {
if _, err = a.emitter.Emit(a.Config, nil); err != nil {
return
}
}
_, err = a.emitter.(data.ManyEmitter).EmitMany(a.Documents, nil)
return
}
func (a *App) Quiet() (err error) {
output := os.Stdout
for _, d := range a.Documents {
for _, dr := range d.Declarations() {
if _, err = output.Write([]byte(fmt.Sprintf("%s\n", dr.Resource().URI()))); err != nil {
return
}
}
}
return
}
func (a *App) Emit() (err error) {
if a.merged == nil {
for _, d := range a.Documents {
slog.Info("Client.Emit() document", "document", d)
if _, err = a.emitter.Emit(d, nil); err != nil {
return
}
}
} else {
if _, err = a.emitter.Emit(a.merged, nil); err != nil {
return
}
}
return
}
func (a *App) Close() (err error) {
if a.emitter != nil {
slog.Info("Client.Close() emitter", "emitter", a.emitter)
return a.emitter.Close()
}
return
}

View File

@ -1,323 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package client
import (
"github.com/stretchr/testify/assert"
"os"
"os/user"
"os/exec"
"testing"
"decl/internal/tempdir"
"log"
"decl/internal/folio"
_ "decl/internal/fan"
"decl/internal/codec"
"decl/internal/data"
"decl/internal/ext"
"context"
"fmt"
"log/slog"
"archive/tar"
"compress/gzip"
"bytes"
"io"
)
var programLevel = new(slog.LevelVar)
var TempDir tempdir.Path = "jx_client"
var ProcessTestUserName string
var ProcessTestGroupName string
func TestMain(m *testing.M) {
LoggerConfig()
err := TempDir.Create()
if err != nil || TempDir == "" {
log.Fatal(err)
}
ProcessTestUserName, ProcessTestGroupName = ProcessUserName()
rc := m.Run()
TempDir.Remove()
os.Exit(rc)
}
func LoggerConfig() {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel}))
slog.SetDefault(logger)
programLevel.Set(slog.LevelDebug)
}
func ProcessUserName() (string, string) {
processUser, userErr := user.Current()
if userErr != nil {
panic(userErr)
}
processGroup, groupErr := user.LookupGroupId(processUser.Gid)
if groupErr != nil {
panic(groupErr)
}
return processUser.Username, processGroup.Name
}
func ExitError(e error) string {
if e != nil {
switch v := e.(type) {
case *exec.ExitError:
return string(v.Stderr)
default:
return e.Error()
}
}
return ""
}
// jx import <docuri>...
func TestClientImport(t *testing.T) {
c := NewClient()
assert.NotNil(t, c)
importDocuments := []string{
"file://../../examples/file.jx.yaml",
"file://../../examples/user.jx.yaml",
}
assert.Nil(t, c.Import(importDocuments))
for index, uri := range importDocuments {
u := folio.URI(uri)
r, readerErr := u.ContentReaderStream()
assert.Nil(t, readerErr)
assert.NotNil(t, r)
doc := folio.DocumentRegistry.NewDocument(folio.URI(uri))
assert.Nil(t, doc.LoadReader(r, codec.FormatYaml))
imported := c.Documents[index]
assert.NotNil(t, imported)
assert.Equal(t, uri, imported.GetURI())
assert.Equal(t, doc.Len(), imported.Len())
}
}
// jx import --resource <resource>
func TestClientImportResource(t *testing.T) {
ctx := context.Background()
c := NewClient()
assert.NotNil(t, c)
importResources := []string{
"file://../../COPYRIGHT",
}
for _, uri := range importResources {
assert.Nil(t, c.ImportResource(ctx, uri))
}
imported := c.Documents[0]
assert.NotNil(t, imported)
for _, uri := range importResources {
assert.NotNil(t, imported.(*folio.Document).GetResource(uri))
}
}
func TestClientEmit(t *testing.T) {
//ctx := context.Background()
c := NewClient()
assert.NotNil(t, c)
importDocuments := []string{
"file://../../examples/file.jx.yaml",
"file://../../examples/user.jx.yaml",
}
assert.Nil(t, c.Import(importDocuments))
targetFile := TempDir.FilePath("jx_emit_output.jx.yaml")
targetFileURI := fmt.Sprintf("file://%s", targetFile)
assert.Nil(t, c.SetOutput(targetFile))
assert.Nil(t, c.Emit())
assert.FileExists(t, targetFile)
u := folio.URI(targetFileURI)
r, readerErr := u.ContentReaderStream()
assert.Nil(t, readerErr)
assert.NotNil(t, r)
extractor, err := folio.DocumentRegistry.ConverterTypes.New(targetFileURI)
assert.Nil(t, err)
assert.NotNil(t, extractor)
targetResource, resErr := u.NewResource(nil)
assert.Nil(t, resErr)
docs, exErr := extractor.(data.ManyExtractor).ExtractMany(targetResource, nil)
assert.Nil(t, exErr)
assert.Equal(t, 2, len(docs))
assert.Equal(t, 1, docs[1].Len())
}
func BenchmarkClientSystemConfigurations(b *testing.B) {
assert.Nil(b, TempDir.Mkdir("benchconfig", 0700))
ConfDir := tempdir.Path(TempDir.FilePath("benchconfig"))
assert.Nil(b, ConfDir.CreateFile("cfg.jx.yaml", `
configurations:
- name: files
values:
prefix: /usr
`))
configDirURI := fmt.Sprintf("file://%s", ConfDir)
programLevel.Set(slog.LevelError)
b.Run("systemconfiguration", func(b *testing.B) {
for i := 0; i < b.N; i++ {
c := NewClient()
_ = c.SystemConfiguration(configDirURI)
}
})
programLevel.Set(slog.LevelDebug)
}
func TestClientSystemConfiguration(t *testing.T) {
c := NewClient()
assert.NotNil(t, c)
assert.Nil(t, TempDir.Mkdir("config", 0700))
ConfDir := tempdir.Path(TempDir.FilePath("config"))
assert.Nil(t, ConfDir.CreateFile("cfg.jx.yaml", `
configurations:
- name: files
values:
prefix: /usr
`))
//configDirURI := fmt.Sprintf("file://%s", ConfDir)
configErr := c.SystemConfiguration(string(ConfDir))
assert.Nil(t, configErr)
assert.NotNil(t, c.Config)
slog.Info("TestClientSystemConfiguration", "config", c.Config)
cfg := c.Config.GetConfig("files")
assert.NotNil(t, cfg)
value, valueErr := cfg.GetValue("prefix")
assert.Nil(t, valueErr)
assert.Equal(t, "/usr", value.(string))
}
func TestClientApply(t *testing.T) {
ctx := context.Background()
c := NewClient()
assert.NotNil(t, c)
assert.Nil(t, TempDir.Mkdir("apply", 0700))
ApplyDir := tempdir.Path(TempDir.FilePath("apply"))
DocSource := ApplyDir.FilePath("res.jx.yaml")
TestFile := ApplyDir.FilePath("testfile.txt")
assert.Nil(t, ApplyDir.CreateFile("res.jx.yaml", fmt.Sprintf(`
resources:
- type: file
transition: create
attributes:
path: %s
content: |
a test string
owner: %s
group: %s
mode: 0644
`, TestFile, ProcessTestUserName, ProcessTestGroupName)))
assert.Nil(t, c.Import([]string{DocSource}))
assert.Nil(t, c.LoadDocumentImports())
assert.Nil(t, c.Apply(ctx, false))
assert.FileExists(t, TestFile)
assert.Nil(t, c.Apply(ctx, true))
assert.NoFileExists(t, TestFile)
}
var tarArchiveBuffer bytes.Buffer
func TarArchive(compress bool) (err error) {
var fileWriter io.WriteCloser
if compress {
gz := gzip.NewWriter(&tarArchiveBuffer)
defer gz.Close()
fileWriter = gz
} else {
fileWriter = ext.WriteNopCloser(&tarArchiveBuffer)
}
tw := tar.NewWriter(fileWriter)
fileContent := "test file content"
if err = tw.WriteHeader(&tar.Header{
Name: "testfile",
Mode: 0600,
Size: int64(len(fileContent)),
}); err == nil {
_, err = tw.Write([]byte(fileContent))
}
tw.Close()
return
}
func TestClientConverters(t *testing.T) {
for _, v := range []struct { Expected data.TypeName; URI string } {
{ Expected: data.TypeName("dir"), URI: "file:///tmp" },
{ Expected: data.TypeName("http"), URI: "https://localhost/test" },
{ Expected: data.TypeName("iptable"), URI: "iptable://filter/INPUT" },
{ Expected: data.TypeName("jx"), URI: "file:///tmp/test.jx.yaml" },
{ Expected: data.TypeName("package"), URI: "package://" },
{ Expected: data.TypeName("container"), URI: "container://" },
{ Expected: data.TypeName("user"), URI: "user://" },
{ Expected: data.TypeName("group"), URI: "group://" },
{ Expected: data.TypeName("tar"), URI: "tar://" },
{ Expected: data.TypeName("tar"), URI: "file:///tmp/foo.tar" },
{ Expected: data.TypeName("tar"), URI: "file:///tmp/foo.tar.gz" },
{ Expected: data.TypeName("tar"), URI: "file:///tmp/foo.tgz" },
} {
c, e := folio.DocumentRegistry.ConverterTypes.New(v.URI)
assert.Nil(t, e)
assert.NotNil(t, c)
assert.Equal(t, v.Expected, c.Type())
}
}
func TestClientImportTar(t *testing.T) {
c := NewClient()
assert.NotNil(t, c)
e := TarArchive(true)
assert.Nil(t, e)
assert.Greater(t, tarArchiveBuffer.Len(), 0)
path, err := TempDir.CreateFileFromReader("test.tar.gz", &tarArchiveBuffer)
assert.Nil(t, err)
uri := fmt.Sprintf("file://%s", path)
d := folio.NewDeclaration()
assert.Nil(t, d.NewResource(&uri))
docs, importErr := c.ImportSource(uri)
assert.Nil(t, importErr)
assert.Greater(t, len(docs), 0)
}

View File

@ -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)
}
}

View File

@ -1,70 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package codec
import (
"encoding/json"
_ "fmt"
_ "github.com/xeipuuv/gojsonschema"
"gopkg.in/yaml.v3"
"io"
"log/slog"
"strings"
"google.golang.org/protobuf/proto"
)
//type JSONDecoder json.Decoder
type Decoder interface {
Decode(v any) error
}
func NewDecoder(r io.Reader, format Format) Decoder {
switch format {
case FormatYaml:
return NewYAMLDecoder(r)
case FormatJson:
return NewJSONDecoder(r)
case FormatProtoBuf:
return NewProtoBufDecoder(r)
}
return nil
}
func NewStringDecoder(s string, format Format) Decoder {
return NewDecoder(strings.NewReader(s), format)
}
func NewJSONDecoder(r io.Reader) Decoder {
return json.NewDecoder(r)
}
func NewJSONStringDecoder(s string) Decoder {
return json.NewDecoder(strings.NewReader(s))
}
func NewYAMLDecoder(r io.Reader) Decoder {
slog.Info("NewYAMLDecoder()", "reader", r)
return yaml.NewDecoder(r)
}
func NewYAMLStringDecoder(s string) Decoder {
return yaml.NewDecoder(strings.NewReader(s))
}
type ProtoDecoder struct {
reader io.Reader
}
func (p *ProtoDecoder) Decode(v any) (err error) {
var protoData []byte
protoData, err = io.ReadAll(p.reader)
if err == nil {
err = proto.Unmarshal(protoData, v.(proto.Message))
}
return
}
func NewProtoBufDecoder(r io.Reader) Decoder {
return &ProtoDecoder{ reader: r }
}

View File

@ -1,134 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package codec
import (
_ "fmt"
"github.com/stretchr/testify/assert"
_ "log"
"strings"
"testing"
"github.com/xeipuuv/gojsonschema"
"io"
"bytes"
"google.golang.org/protobuf/proto"
)
type TestUser struct {
Name string `json:"name" yaml:"name" protobuf:"bytes,1,opt,name=name"`
Uid string `json:"uid" yaml:"uid" protobuf:"bytes,2,opt,name=uid"`
Group string `json:"group" yaml:"group" protobuf:"bytes,3,opt,name=group"`
Home string `json:"home" yaml:"home" protobuf:"bytes,4,opt,name=home"`
State string `json:"state" yaml:"state" protobuf:"bytes,5,opt,name=state"`
}
func TestNewYAMLDecoder(t *testing.T) {
e := NewYAMLDecoder(strings.NewReader(""))
assert.NotNil(t, e)
}
func TestNewDecoderDecodeJSON(t *testing.T) {
schema:=`
{
"$id": "user",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "user",
"type": "object",
"required": [ "name" ],
"properties": {
"path": {
"type": "string",
"description": "user name",
"minLength": 1
}
}
}
`
decl := `{
"name": "testuser",
"uid": "12001",
"group": "12001",
"home": "/home/testuser",
"state": "present"
}`
jsonReader := strings.NewReader(decl)
user := &TestUser{}
e := NewJSONDecoder(jsonReader)
assert.NotNil(t, e)
docErr := e.Decode(user)
assert.Nil(t, docErr)
schemaLoader := gojsonschema.NewStringLoader(schema)
loader := gojsonschema.NewStringLoader(decl)
result, validateErr := gojsonschema.Validate(schemaLoader, loader)
assert.True(t, result.Valid())
assert.Nil(t, validateErr)
}
func TestNewJSONStringDecoder(t *testing.T) {
decl := `{
"name": "testuser",
"uid": "12001",
"group": "12001",
"home": "/home/testuser",
"state": "present"
}`
e := NewJSONStringDecoder(decl)
assert.NotNil(t, e)
docErr := e.Decode(&TestUser{})
assert.Nil(t, docErr)
}
func TestNewDecoder(t *testing.T) {
pbData, err := proto.Marshal(&TestPBUser{ Name: "pb", Uid: "15001", Group: "15005", Home: "/home/pb", State: "present" })
assert.Nil(t, err)
for _, v := range []struct{ reader io.Reader; format Format; expectedhome string } {
{ reader: strings.NewReader(`{
"name": "testuser",
"uid": "12001",
"group": "12001",
"home": "/home/testuser",
"state": "present" }`), format: FormatJson, expectedhome: "/home/testuser" },
{ reader: strings.NewReader(`
name: "testuser"
uid: "12001"
group: "12001"
home: "/home/test"
state: "present"
`), format: FormatYaml, expectedhome: "/home/test" },
{ reader: bytes.NewReader(pbData), format: FormatProtoBuf, expectedhome: "/home/pb" },
} {
decoder := NewDecoder(v.reader, v.format)
assert.NotNil(t, decoder)
u := &TestPBUser{}
assert.Nil(t, decoder.Decode(u))
assert.Equal(t, v.expectedhome, u.Home )
}
}
func TestNewDecoderError(t *testing.T) {
pbData, err := proto.Marshal(&TestPBUser{ Name: "pb", Uid: "15001", Group: "15005", Home: "/home/pb", State: "present" })
assert.Nil(t, err)
decoder := NewDecoder(bytes.NewReader(pbData), Format("foo"))
assert.Nil(t, decoder)
}
func TestNewStringDecoder(t *testing.T) {
jsonDoc := `{
"name": "testuser",
"uid": "12001",
"group": "12001",
"home": "/home/testuser",
"state": "present" }`
decoder := NewStringDecoder(jsonDoc, FormatJson)
assert.NotNil(t, decoder)
u := &TestUser{}
assert.Nil(t, decoder.Decode(u))
assert.Equal(t, "testuser", u.Name)
}

View File

@ -1,76 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package codec
import (
"encoding/json"
_ "fmt"
_ "github.com/xeipuuv/gojsonschema"
"gopkg.in/yaml.v3"
"io"
_ "log"
"errors"
"google.golang.org/protobuf/proto"
)
var ErrInvalidWriter error = errors.New("Invalid writer")
type JSONEncoder json.Encoder
type Encoder interface {
Encode(v any) error
Close() error
}
func NewEncoder(w io.Writer, format Format) Encoder {
switch format {
case FormatYaml:
return NewYAMLEncoder(w)
case FormatJson:
return NewJSONEncoder(w)
case FormatProtoBuf:
return NewProtoBufEncoder(w)
}
return nil
}
func NewJSONEncoder(w io.Writer) Encoder {
return (*JSONEncoder)(json.NewEncoder(w))
}
func NewYAMLEncoder(w io.Writer) Encoder {
return yaml.NewEncoder(w)
}
type ProtoEncoder struct {
writer io.Writer
}
func (p *ProtoEncoder) Encode(v any) (err error) {
var encoded []byte
encoded, err = proto.Marshal(v.(proto.Message))
if err != nil {
return
}
_, err = p.writer.Write(encoded)
return
}
func (p *ProtoEncoder) Close() error {
return nil
}
func NewProtoBufEncoder(w io.Writer) Encoder {
if w != nil {
return &ProtoEncoder{ writer: w }
}
return nil
}
func (j *JSONEncoder) Encode(v any) error {
return (*json.Encoder)(j).Encode(v)
}
func (j *JSONEncoder) Close() error {
return nil
}

View File

@ -1,110 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package codec
import (
_ "fmt"
"github.com/stretchr/testify/assert"
_ "log"
"strings"
"testing"
"github.com/xeipuuv/gojsonschema"
"io"
"bytes"
"google.golang.org/protobuf/proto"
)
type TestFile struct {
Path string `json:"path" yaml:"path"`
}
func TestNewYAMLEncoder(t *testing.T) {
var yamlDoc strings.Builder
e := NewYAMLEncoder(&yamlDoc)
assert.NotNil(t, e)
}
func TestNewEncoderEncodeJSON(t *testing.T) {
schema:=`
{
"$id": "file",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "file",
"type": "object",
"required": [ "path" ],
"properties": {
"path": {
"type": "string",
"description": "file path",
"minLength": 1
}
}
}
`
var jsonDoc strings.Builder
file := &TestFile{}
file.Path = "foo"
e := NewJSONEncoder(&jsonDoc)
assert.NotNil(t, e)
docErr := e.Encode(file)
assert.Nil(t, docErr)
schemaLoader := gojsonschema.NewStringLoader(schema)
loader := gojsonschema.NewStringLoader(jsonDoc.String())
result, err := gojsonschema.Validate(schemaLoader, loader)
assert.Nil(t, err)
assert.True(t, result.Valid())
}
func TestNewEncoder(t *testing.T) {
pb := &TestPBUser{ Name: "pb", Uid: "15001", Group: "15005", Home: "/home/pb", State: "present" }
jx := &TestUser{ Name: "jx", Uid: "17001", Group: "17005", Home: "/home/jx", State: "present" }
pbData, pbErr := proto.Marshal(pb)
assert.Nil(t, pbErr)
for _, v := range []struct{ writer io.Writer; testuser any; format Format; expected []byte} {
{ writer: &bytes.Buffer{}, testuser: jx, expected: []byte(`{"name":"jx","uid":"17001","group":"17005","home":"/home/jx","state":"present"}
`), format: FormatJson },
{ writer: &bytes.Buffer{}, testuser: jx, expected: []byte(`name: jx
uid: "17001"
group: "17005"
home: /home/jx
state: present
`), format: FormatYaml },
{ writer: &bytes.Buffer{}, testuser: pb, expected: pbData , format: FormatProtoBuf },
} {
encoder := NewEncoder(v.writer, v.format)
assert.NotNil(t, encoder)
assert.Nil(t, encoder.Encode(v.testuser))
assert.Equal(t, string(v.expected), v.writer.(*bytes.Buffer).String())
assert.Equal(t, v.expected, v.writer.(*bytes.Buffer).Bytes())
assert.Nil(t, encoder.Close())
}
}
func TestNewEncoderError(t *testing.T) {
encoder := NewEncoder(&strings.Builder{}, Format("foo"))
assert.Nil(t, encoder)
}
func TestNewProtobufError(t *testing.T) {
encoder := NewProtoBufEncoder(nil)
assert.Nil(t, encoder)
}
/*
func TestProtobufEncodeError(t *testing.T) {
buf := &bytes.Buffer{}
buf.Write([]byte("broken input"))
encoder := NewProtoBufEncoder(buf)
assert.NotNil(t, encoder)
assert.NotNil(t, encoder.Encode(&TestPBUser{}))
}
*/

View File

@ -1,179 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.34.2
// protoc v3.12.4
// source: testuser.proto
package codec
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type TestPBUser struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
Uid string `protobuf:"bytes,2,opt,name=Uid,proto3" json:"Uid,omitempty"`
Group string `protobuf:"bytes,3,opt,name=Group,proto3" json:"Group,omitempty"`
Home string `protobuf:"bytes,4,opt,name=Home,proto3" json:"Home,omitempty"`
State string `protobuf:"bytes,5,opt,name=State,proto3" json:"State,omitempty"`
}
func (x *TestPBUser) Reset() {
*x = TestPBUser{}
if protoimpl.UnsafeEnabled {
mi := &file_testuser_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TestPBUser) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TestPBUser) ProtoMessage() {}
func (x *TestPBUser) ProtoReflect() protoreflect.Message {
mi := &file_testuser_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TestPBUser.ProtoReflect.Descriptor instead.
func (*TestPBUser) Descriptor() ([]byte, []int) {
return file_testuser_proto_rawDescGZIP(), []int{0}
}
func (x *TestPBUser) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *TestPBUser) GetUid() string {
if x != nil {
return x.Uid
}
return ""
}
func (x *TestPBUser) GetGroup() string {
if x != nil {
return x.Group
}
return ""
}
func (x *TestPBUser) GetHome() string {
if x != nil {
return x.Home
}
return ""
}
func (x *TestPBUser) GetState() string {
if x != nil {
return x.State
}
return ""
}
var File_testuser_proto protoreflect.FileDescriptor
var file_testuser_proto_rawDesc = []byte{
0x0a, 0x0e, 0x74, 0x65, 0x73, 0x74, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x05, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x22, 0x72, 0x0a, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x50,
0x42, 0x55, 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x69, 0x64,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x47,
0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x47, 0x72, 0x6f, 0x75,
0x70, 0x12, 0x12, 0x0a, 0x04, 0x48, 0x6f, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x48, 0x6f, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x42, 0x15, 0x5a, 0x13, 0x64,
0x65, 0x63, 0x6c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x6f, 0x64,
0x65, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_testuser_proto_rawDescOnce sync.Once
file_testuser_proto_rawDescData = file_testuser_proto_rawDesc
)
func file_testuser_proto_rawDescGZIP() []byte {
file_testuser_proto_rawDescOnce.Do(func() {
file_testuser_proto_rawDescData = protoimpl.X.CompressGZIP(file_testuser_proto_rawDescData)
})
return file_testuser_proto_rawDescData
}
var file_testuser_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_testuser_proto_goTypes = []any{
(*TestPBUser)(nil), // 0: codec.TestPBUser
}
var file_testuser_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_testuser_proto_init() }
func file_testuser_proto_init() {
if File_testuser_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_testuser_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*TestPBUser); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_testuser_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_testuser_proto_goTypes,
DependencyIndexes: file_testuser_proto_depIdxs,
MessageInfos: file_testuser_proto_msgTypes,
}.Build()
File_testuser_proto = out.File
file_testuser_proto_rawDesc = nil
file_testuser_proto_goTypes = nil
file_testuser_proto_depIdxs = nil
}

View File

@ -1,13 +0,0 @@
syntax = "proto3";
package codec;
option go_package = "decl/internal/codec";
message TestPBUser {
string Name = 1;
string Uid = 2;
string Group = 3;
string Home = 4;
string State = 5;
}

View File

@ -1,87 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package codec
import (
"io"
"fmt"
"errors"
"encoding/json"
"gopkg.in/yaml.v3"
)
const (
FormatYml Format = "yml"
FormatYaml Format = "yaml"
FormatJson Format = "json"
FormatProtoBuf Format = "protobuf"
)
var ErrInvalidFormat error = errors.New("invalid Format value")
type Format string
func (f *Format) Validate() error {
switch *f {
case FormatYml, FormatYaml, FormatJson, FormatProtoBuf:
return nil
default:
return fmt.Errorf("%w: %s", ErrInvalidFormat, *f)
}
}
func (f *Format) Set(value string) (err error) {
if err = (*Format)(&value).Validate(); err == nil {
err = f.UnmarshalValue(value)
}
return
}
func (f *Format) UnmarshalValue(value string) error {
switch value {
case string(FormatYml):
*f = FormatYaml
case string(FormatYaml), string(FormatJson), string(FormatProtoBuf):
*f = Format(value)
default:
return ErrInvalidFormat
}
return nil
}
func (f *Format) UnmarshalJSON(data []byte) error {
var s string
if unmarshalFormatTypeErr := json.Unmarshal(data, &s); unmarshalFormatTypeErr != nil {
return unmarshalFormatTypeErr
}
return f.UnmarshalValue(s)
}
func (f *Format) UnmarshalYAML(value *yaml.Node) error {
var s string
if err := value.Decode(&s); err != nil {
return err
}
return f.UnmarshalValue(s)
}
func (f Format) Encoder(w io.Writer) Encoder {
return NewEncoder(w, f)
}
func (f Format) Decoder(r io.Reader) Decoder {
return NewDecoder(r, f)
}
func (f Format) StringDecoder(s string) Decoder {
return NewStringDecoder(s, f)
}
func (f Format) Serialize(object any, w io.Writer) error {
return f.Encoder(w).Encode(object)
}
func (f Format) Deserialize(r io.Reader, object any) error {
return f.Decoder(r).Decode(object)
}

View File

@ -1,77 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package codec
import (
_ "fmt"
"github.com/stretchr/testify/assert"
_ "log"
"testing"
"strings"
"encoding/json"
)
type TestDec struct {
FormatType Format `yaml:"formattype" json:"formattype"`
}
func TestFormatType(t *testing.T) {
yamlData := `
formattype: json
`
v := &TestDec{}
dec := NewYAMLStringDecoder(yamlData)
e := dec.Decode(v)
assert.Nil(t, e)
assert.Equal(t, FormatJson, v.FormatType)
}
func TestFormatTypeErr(t *testing.T) {
yamlData := `
formattype: foo
`
v := &TestDec{}
dec := NewYAMLStringDecoder(yamlData)
e := dec.Decode(v)
assert.ErrorIs(t, ErrInvalidFormat, e)
}
func TestFormatValidate(t *testing.T) {
f := FormatYaml
assert.Nil(t, f.Validate())
var fail Format = Format("foo")
assert.ErrorIs(t, fail.Validate(), ErrInvalidFormat)
var testFormatSet Format
assert.Nil(t, testFormatSet.Set("yaml"))
assert.ErrorIs(t, testFormatSet.Set("yamlv3"), ErrInvalidFormat)
}
func TestFormatCodec(t *testing.T) {
var output map[string]Format = make(map[string]Format)
var writer strings.Builder
encoder := FormatYaml.Encoder(&writer)
assert.NotNil(t, encoder)
decoder := FormatYaml.Decoder(strings.NewReader("formattype: json"))
assert.Nil(t, decoder.Decode(output))
assert.Equal(t, FormatJson, output["formattype"])
}
func TestFormatUnmarshal(t *testing.T) {
var f Format
assert.Nil(t, json.Unmarshal([]byte("\"yaml\""), &f))
assert.Equal(t, FormatYaml, f)
assert.NotNil(t, json.Unmarshal([]byte("\"yaml"), &f))
assert.Nil(t, json.Unmarshal([]byte("\"yml\""), &f))
assert.Equal(t, FormatYaml, f)
}

View File

@ -1,214 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package command
import (
_ "context"
"fmt"
"errors"
"io"
"log/slog"
_ "net/url"
"os/exec"
"strings"
"text/template"
"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")
type CommandExecutor func(value any) ([]byte, error)
type CommandExtractAttributes func(output []byte, target any) error
type CommandExists func() error
type CommandInput string
type Command struct {
Path string `json:"path" yaml:"path"`
Args []CommandArg `json:"args" yaml:"args"`
Env []string `json:"env" yaml:"env"`
Split bool `json:"split" yaml:"split"`
FailOnError bool `json:"failonerror" yaml:"failonerror"`
StdinAvailable bool `json:"stdinavailable,omitempty" yaml:"stdinavailable,omitempty"`
ExitCode int `json:"exitcode,omitempty" yaml:"exitcode,omitempty"`
Stdout string `json:"stdout,omitempty" yaml:"stdout,omitempty"`
Stderr string `json:"stderr,omitempty" yaml:"stderr,omitempty"`
Executor CommandExecutor `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 {
c := &Command{ Split: true, FailOnError: true }
c.Defaults()
return c
}
func (c *Command) ClearOutput() {
c.Stdout = ""
c.Stderr = ""
c.ExitCode = 0
}
func (c *Command) Defaults() {
c.ClearOutput()
c.Split = true
c.FailOnError = true
c.CommandExists = func() error {
if _, err := exec.LookPath(c.Path); err != nil {
return fmt.Errorf("%w - %w", ErrUnknownCommand, err)
}
return nil
}
c.Executor = func(value any) ([]byte, error) {
c.ClearOutput()
c.execHandle = c.TargetRef.Provider(c, value)
if inputErr := c.SetInput(value); inputErr != nil {
return nil, inputErr
}
c.SetCmdEnv()
cmd := c.execHandle
if c.stdin != nil {
cmd.SetStdin(c.stdin)
}
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)
if len(stdOutOutput) > 100 {
slog.Info("execute() - io", "stdout", string(stdOutOutput[:100]), "stderr", string(stdErrOutput))
} else {
slog.Info("execute() - io", "stdout", string(stdOutOutput), "stderr", string(stdErrOutput))
}
waitErr := cmd.Wait()
c.Stdout = string(stdOutOutput)
c.Stderr = string(stdErrOutput)
c.ExitCode = c.GetExitCodeFromError(waitErr)
/*
if len(stdOutOutput) > 100 {
slog.Info("execute()", "path", c.Path, "args", args, "output", string(stdOutOutput[:100]), "error", string(stdErrOutput))
} else {
slog.Info("execute()", "path", c.Path, "args", args, "output", string(stdOutOutput), "error", string(stdErrOutput))
}
*/
if len(stdErrOutput) > 0 && c.FailOnError {
return stdOutOutput, fmt.Errorf("%w %s", waitErr, string(stdErrOutput))
}
return stdOutOutput, waitErr
}
}
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) SetCmdEnv() {
c.execHandle.SetCmdEnv(c.Env)
}
func (c *Command) SetStdinReader(r io.Reader) {
if c.StdinAvailable {
c.stdin = r
}
}
func (c *Command) Exists() bool {
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) {
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 *Command) SetInput(value any) error {
if len(c.Input) > 0 {
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
}
func (c *CommandInput) Template(value any) (result strings.Builder, err error) {
err = template.Must(template.New("commandInput").Parse(string(*c))).Execute(&result, value)
return
}

View File

@ -1,102 +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 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 := 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 TestCommandStdin(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 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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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())
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,103 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package config
import (
"context"
"io"
"fmt"
"net/url"
"decl/internal/codec"
"decl/internal/data"
"decl/internal/folio"
"encoding/json"
"gopkg.in/yaml.v3"
"crypto/x509"
)
func init() {
folio.DocumentRegistry.ConfigurationTypes.Register([]string{"certificate"}, func(u *url.URL) data.Configuration {
c := NewCertificate()
return c
})
}
type Certificate map[string]*x509.Certificate
func NewCertificate() *Certificate {
c := make(Certificate)
return &c
}
func (c *Certificate) URI() string {
return fmt.Sprintf("%s://%s", c.Type(), "")
}
func (c *Certificate) SetURI(uri string) error {
return nil
}
func (c *Certificate) SetParsedURI(uri data.URIParser) error {
return nil
}
func (c *Certificate) Read(ctx context.Context) ([]byte, error) {
return nil, nil
}
func (c *Certificate) Load(r io.Reader) (err error) {
err = codec.NewYAMLDecoder(r).Decode(c)
if err == nil {
_, err = c.Read(context.Background())
}
return err
}
func (c *Certificate) LoadYAML(yamlData string) (err error) {
err = codec.NewYAMLStringDecoder(yamlData).Decode(c)
if err == nil {
_, err = c.Read(context.Background())
}
return err
}
func (c *Certificate) UnmarshalJSON(data []byte) error {
if unmarshalErr := json.Unmarshal(data, c); unmarshalErr != nil {
return unmarshalErr
}
return nil
}
func (c *Certificate) UnmarshalYAML(value *yaml.Node) error {
type decodeCertificate Certificate
if unmarshalErr := value.Decode((*decodeCertificate)(c)); unmarshalErr != nil {
return unmarshalErr
}
return nil
}
func (c *Certificate) Clone() data.Configuration {
jsonGeneric, _ := json.Marshal(c)
clone := NewCertificate()
if unmarshalErr := json.Unmarshal(jsonGeneric, &clone); unmarshalErr != nil {
panic(unmarshalErr)
}
return clone
}
func (c *Certificate) Type() string {
return "certificate"
}
func (c *Certificate) GetValue(name string) (result any, err error) {
var ok bool
if result, ok = (*c)[name]; !ok {
err = data.ErrUnknownConfigurationKey
}
return
}
func (c *Certificate) Has(key string) (ok bool) {
_, ok = (*c)[key]
return
}

View File

@ -1,33 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package config
import (
"github.com/stretchr/testify/assert"
"testing"
"crypto/x509"
)
func TestNewCertificateConfig(t *testing.T) {
c := NewCertificate()
assert.NotNil(t, c)
}
func TestNewCertificateConfigYAML(t *testing.T) {
c := NewCertificate()
assert.NotNil(t, c)
config := `
catemplate:
subject:
organization:
- RKH
notbefore: 2024-07-10
`
yamlErr := c.LoadYAML(config)
assert.Nil(t, yamlErr)
crt, err := c.GetValue("catemplate")
assert.Nil(t, err)
assert.Equal(t, []string{"RKH"}, crt.(*x509.Certificate).Subject.Organization)
}

View File

@ -1,9 +0,0 @@
configurations:
- name: facts
type: exec
values:
path: /usr/bin/facter
args:
- "-j"
format: "json"

View File

@ -1,140 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package config
import (
"context"
"io"
"fmt"
"net/url"
"decl/internal/codec"
"decl/internal/command"
"decl/internal/data"
"decl/internal/folio"
"encoding/json"
"gopkg.in/yaml.v3"
)
func init() {
folio.DocumentRegistry.ConfigurationTypes.Register([]string{"exec"}, func(u *url.URL) data.Configuration {
x := NewExec()
return x
})
}
type Exec struct {
Path string `yaml:"path" json:"path"`
Args []command.CommandArg `yaml:"args" json:"args"`
ValuesFormat codec.Format `yaml:"format" json:"format"`
Values map[string]any `yaml:"values" json:"values"`
ReadCommand *command.Command `yaml:"-" json:"-"`
}
func NewExec() *Exec {
x := &Exec{}
return x
}
func (x *Exec) SetURI(uri string) error {
return nil
}
func (x *Exec) SetParsedURI(uri data.URIParser) error {
return nil
}
func (x *Exec) URI() string {
return fmt.Sprintf("%s://%s", x.Type(), x.Path)
}
func (x *Exec) Read(ctx context.Context) ([]byte, error) {
out, err := x.ReadCommand.Execute(x)
if err != nil {
return nil, err
}
exErr := x.ReadCommand.Extractor(out, x)
if exErr != nil {
return nil, exErr
}
return nil, exErr
}
func (x *Exec) Load(r io.Reader) (err error) {
err = codec.NewYAMLDecoder(r).Decode(x)
if err == nil {
_, err = x.Read(context.Background())
}
return err
}
func (x *Exec) LoadYAML(yamlData string) (err error) {
err = codec.NewYAMLStringDecoder(yamlData).Decode(x)
if err == nil {
_, err = x.Read(context.Background())
}
return err
}
func (x *Exec) UnmarshalJSON(data []byte) error {
if unmarshalErr := json.Unmarshal(data, x); unmarshalErr != nil {
return unmarshalErr
}
x.NewReadConfigCommand()
return nil
}
func (x *Exec) UnmarshalYAML(value *yaml.Node) error {
type decodeExec Exec
if unmarshalErr := value.Decode((*decodeExec)(x)); unmarshalErr != nil {
return unmarshalErr
}
x.NewReadConfigCommand()
return nil
}
func (x *Exec) Clone() data.Configuration {
clone := NewExec()
clone.Path = x.Path
clone.Args = x.Args
clone.ValuesFormat = x.ValuesFormat
clone.Values = x.Values
clone.ReadCommand = x.ReadCommand
return clone
}
func (x *Exec) Type() string {
return "exec"
}
func (x *Exec) GetValue(name string) (result any, err error) {
var ok bool
if result, ok = x.Values[name]; !ok {
err = data.ErrUnknownConfigurationKey
}
return
}
func (x *Exec) Has(key string) (ok bool) {
_, ok = x.Values[key]
return
}
func (ex *Exec) NewReadConfigCommand() {
ex.ReadCommand = command.NewCommand()
ex.ReadCommand.Path = ex.Path
ex.ReadCommand.Args = ex.Args
ex.ReadCommand.Extractor = func(out []byte, target any) error {
x := target.(*Exec)
switch x.ValuesFormat {
case codec.FormatYaml:
return codec.NewYAMLStringDecoder(string(out)).Decode(&x.Values)
case codec.FormatJson:
return codec.NewJSONStringDecoder(string(out)).Decode(&x.Values)
case codec.FormatProtoBuf:
}
return nil
}
}

View File

@ -1,86 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package config
import (
"context"
"encoding/json"
"net/url"
"fmt"
"decl/internal/data"
"decl/internal/folio"
"decl/internal/codec"
"io"
)
func init() {
folio.DocumentRegistry.ConfigurationTypes.Register([]string{"generic"}, func(u *url.URL) data.Configuration {
g := NewGeneric[any]()
return g
})
}
type Generic[Value any] map[string]Value
func NewGeneric[Value any]() *Generic[Value] {
g := make(Generic[Value])
return &g
}
func (g *Generic[Value]) URI() string {
return fmt.Sprintf("%s://%s", g.Type(), "")
}
func (g *Generic[Value]) SetURI(uri string) error {
return nil
}
func (g *Generic[Value]) SetParsedURI(uri data.URIParser) error {
return nil
}
func (g *Generic[Value]) Load(r io.Reader) (err error) {
err = codec.NewYAMLDecoder(r).Decode(g)
if err == nil {
_, err = g.Read(context.Background())
}
return err
}
func (g *Generic[Value]) LoadYAML(yamlData string) (err error) {
err = codec.NewYAMLStringDecoder(yamlData).Decode(g)
if err == nil {
_, err = g.Read(context.Background())
}
return err
}
func (g *Generic[Value]) Clone() data.Configuration {
jsonGeneric, _ := json.Marshal(g)
clone := NewGeneric[Value]()
if unmarshalErr := json.Unmarshal(jsonGeneric, clone); unmarshalErr != nil {
panic(unmarshalErr)
}
return clone
}
func (g *Generic[Value]) Type() string {
return "generic"
}
func (g *Generic[Value]) Read(context.Context) ([]byte, error) {
return nil, nil
}
func (g *Generic[Value]) GetValue(name string) (result any, err error) {
var ok bool
if result, ok = (*g)[name]; !ok {
err = data.ErrUnknownConfigurationKey
}
return
}
func (g *Generic[Value]) Has(key string) (ok bool) {
_, ok = (*g)[key]
return
}

View File

@ -1,13 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package config
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestNewGenericConfig(t *testing.T) {
g := NewGeneric[any]()
assert.NotNil(t, g)
}

View File

@ -1,90 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package config
import (
"crypto/x509"
"crypto/x509/pkix"
"crypto/rsa"
"crypto/rand"
"encoding/pem"
"encoding/json"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
)
type OpenPGP struct {
Armored string
entities openpgp.EntityList
}
func (o *OpenPGP) Read() (yamlData []byte, err error) {
pemReader := io.NopCloser(strings.NewReader(o.Armored))
o.entities, err = openpgp.ReadArmoredKeyRing(pemReader)
return
}
func (o *OpenPGP) UnmarshalJSON(data []byte) error {
if unmarshalErr := json.Unmarshal(data, o); unmarshalErr != nil {
return unmarshalErr
}
return nil
}
func (o *OpenPGP) UnmarshalYAML(value *yaml.Node) error {
type decodeOpenPGP OpenPGP
if unmarshalErr := value.Decode((*decodeOpenPGP)(o)); unmarshalErr != nil {
return unmarshalErr
}
return nil
}
func (o *OpenPGP) Clone() data.Configuration {
jsonGeneric, _ := json.Marshal(c)
clone := NewOpenPGP()
if unmarshalErr := json.Unmarshal(jsonGeneric, &clone); unmarshalErr != nil {
panic(unmarshalErr)
}
return clone
}
func (o *OpenPGP) Type() string {
return "openpgp"
}
func (o *OpenPGP) 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 *OpenPGP) GetValue(name string) (result any, err error) {
var ok bool
if result, ok = (*c)[name]; !ok {
err = data.ErrUnknownConfigurationKey
}
return
}
// Expected key: 0.PrivateKey
func (o *OpenPGP) Has(key string) (ok bool) {
index, field, err := o.GetEntityIndex(key)
if len(o.entities) > index && err == nil {
switch key {
case PublicKey:
ok = o.entities[index].PrimaryKey != nil
case PrivateKey:
ok = o.entities[index].PrimaryKey != nil
}
}
return
}

View File

@ -1,31 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package config
import (
"github.com/stretchr/testify/assert"
"testing"
"crypto/x509"
)
func TestNewOpenPGPConfig(t *testing.T) {
p := NewOpenPGP()
assert.NotNil(t, p)
}
func TestNewOpenPGPConfigYAML(t *testing.T) {
p := NewOpenPGP()
assert.NotNil(t, p)
config := `
openpgp:
publickey:
`
yamlErr := c.LoadYAML(config)
assert.Nil(t, yamlErr)
crt, err := c.GetValue("catemplate")
assert.Nil(t, err)
assert.Equal(t, []string{"RKH"}, crt.(*x509.Certificate).Subject.Organization)
}

View File

@ -1,63 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package config
import (
"errors"
"fmt"
"github.com/xeipuuv/gojsonschema"
"strings"
"embed"
"net/http"
"log/slog"
"decl/internal/folio"
)
//go:embed schemas/*.schema.json
var schemaFiles embed.FS
var schemaFilesUri folio.URI = "file://config/schemas/*.schema.json"
func init() {
folio.DocumentRegistry.Schemas[schemaFilesUri] = schemaFiles
folio.DocumentRegistry.DefaultSchema = schemaFilesUri
}
type Schema struct {
schema gojsonschema.JSONLoader
}
func NewSchema(name string) *Schema {
path := fmt.Sprintf("file://schemas/%s.schema.json", name)
return &Schema{schema: gojsonschema.NewReferenceLoaderFileSystem(path, http.FS(schemaFiles))}
}
func (s *Schema) Validate(source string) error {
loader := gojsonschema.NewStringLoader(source)
result, err := gojsonschema.Validate(s.schema, loader)
if err != nil {
slog.Info("schema error", "source", source, "schema", s.schema, "result", result, "err", err)
return err
}
slog.Info("schema", "source", source, "schema", s.schema, "result", result, "err", err)
if !result.Valid() {
schemaErrors := strings.Builder{}
for _, err := range result.Errors() {
schemaErrors.WriteString(err.String() + "\n")
}
schemaErrors.WriteString(source)
return errors.New(schemaErrors.String())
}
return nil
}
func (s *Schema) ValidateSchema() error {
sl := gojsonschema.NewSchemaLoader()
sl.Validate = true
schemaErr := sl.AddSchemas(s.schema)
slog.Info("validate schema definition", "schemaloader", sl, "err", schemaErr)
return schemaErr
}

View File

@ -1,47 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package config
import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)
func TestNewSchema(t *testing.T) {
s := NewSchema("document")
assert.NotEqual(t, nil, s)
}
func TestSchemaValidateJSON(t *testing.T) {
// ctx := context.Background()
s := NewSchema("block")
assert.NotNil(t, s)
assert.Nil(t, s.ValidateSchema())
configBlockYaml := `
type: "generic"
name: "foo"
values:
bar: quuz
`
testConfig := NewGeneric[any]()
e := testConfig.LoadYAML(configBlockYaml)
assert.Nil(t, e)
jsonDoc, jsonErr := json.Marshal(testConfig)
assert.Nil(t, jsonErr)
schemaErr := s.Validate(string(jsonDoc))
assert.Nil(t, schemaErr)
}
func TestSchemaValidateSchema(t *testing.T) {
s := NewSchema("document")
assert.NotNil(t, s)
assert.Nil(t, s.ValidateSchema())
}

View File

@ -1,25 +0,0 @@
{
"$id": "block.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "block",
"type": "object",
"required": [ "name", "values" ],
"properties": {
"name": {
"type": "string",
"description": "Config block name",
"minLength": 2
},
"type": {
"type": "string",
"description": "Config type name.",
"enum": [ "system", "generic", "exec", "certificate" ]
},
"values": {
"oneOf": [
{ "type": "object" },
{ "$ref": "certificate.schema.json" }
]
}
}
}

View File

@ -1,62 +0,0 @@
{
"$id": "certificate.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "certificate",
"type": "object",
"required": [ "path", "filetype" ],
"properties": {
"SerialNumber": {
"type": "integer",
"description": "Serial number",
"minLength": 1
},
"Issuer": {
"$ref": "pkixname.schema.json"
},
"Subject": {
"$ref": "pkixname.schema.json"
},
"NotBefore": {
"type": "string",
"format": "date-time",
"description": "Cert is not valid before time in YYYY-MM-DDTHH:MM:SS.sssssssssZ format."
},
"NotAfter": {
"type": "string",
"format": "date-time",
"description": "Cert is not valid after time in YYYY-MM-DDTHH:MM:SS.sssssssssZ format."
},
"KeyUsage": {
"type": "integer",
"enum": [
1,
2,
3,
4,
5,
6,
7,
8,
9
],
"description": "Actions valid for a key. E.g. 1 = KeyUsageDigitalSignature"
},
"ExtKeyUsage": {
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 13
},
"description": "Extended set of actions valid for a key"
},
"BasicConstraintsValid": {
"type": "boolean",
"description": "BasicConstraintsValid indicates whether IsCA, MaxPathLen, and MaxPathLenZero are valid"
},
"IsCA": {
"type": "boolean",
"description": ""
}
}
}

View File

@ -1,18 +0,0 @@
{
"$id": "config.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "config",
"type": "object",
"required": [ "configurations" ],
"properties": {
"configurations": {
"type": "array",
"description": "Configurations list",
"items": {
"oneOf": [
{ "$ref": "block.schema.json" }
]
}
}
}
}

View File

@ -1,19 +0,0 @@
{
"$id": "document.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "document",
"type": "object",
"required": [ "configurations" ],
"properties": {
"configurations": {
"type": "array",
"description": "Configurations list",
"items": {
"oneOf": [
{ "$ref": "block.schema.json" }
]
}
}
}
}

View File

@ -1,65 +0,0 @@
{
"$id": "pkixname.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "pkixname",
"type": "object",
"properties": {
"Country": {
"type": "array",
"description": "Country name",
"items": {
"type": "string"
}
},
"Organization": {
"type": "array",
"description": "Organization name",
"items": {
"type": "string"
}
},
"OrganizationalUnit": {
"type": "array",
"description": "Organizational Unit name",
"items": {
"type": "string"
}
},
"Locality": {
"type": "array",
"description": "Locality name",
"items": {
"type": "string"
}
},
"Province": {
"type": "array",
"description": "Province name",
"items": {
"type": "string"
}
},
"StreetAddress": {
"type": "array",
"description": "Street address",
"items": {
"type": "string"
}
},
"PostalCode": {
"type": "array",
"description": "Postal Code",
"items": {
"type": "string"
}
},
"SerialNumber": {
"type": "string",
"description": ""
},
"CommonName": {
"type": "string",
"description": "Name"
}
}
}

View File

@ -1,97 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package config
import (
"context"
"encoding/json"
"net/url"
"fmt"
"decl/internal/data"
"decl/internal/folio"
"runtime"
"decl/internal/system"
)
// Collects facts about the system
var (
buildValues = map[string]any{
"GOOS": runtime.GOOS,
"GOARCH": runtime.GOARCH,
}
)
func init() {
folio.DocumentRegistry.ConfigurationTypes.Register([]string{"system"}, func(u *url.URL) data.Configuration {
s := NewSystem()
return s
})
}
type System Generic[any]
func NewSystem() *System {
s := make(System)
for k, v := range buildValues {
s[k] = v
}
s.CurrentUser()
s["importpath"] = []string {
"/etc/jx/lib",
}
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 {
return fmt.Sprintf("%s://%s", s.Type(), "")
}
func (s *System) SetURI(uri string) error {
return nil
}
func (s *System) SetParsedURI(uri data.URIParser) error {
return nil
}
func (s *System) Clone() data.Configuration {
jsonSystem, _ := json.Marshal(s)
clone := NewSystem()
if unmarshalErr := json.Unmarshal(jsonSystem, clone); unmarshalErr != nil {
panic(unmarshalErr)
}
return clone
}
func (s *System) Has(key string) (ok bool) {
_, ok = (*s)[key]
return
}
func (s *System) Type() string {
return "system"
}
func (s *System) Read(context.Context) ([]byte, error) {
return nil, nil
}
func (s *System) GetValue(name string) (result any, err error) {
var ok bool
if result, ok = (*s)[name]; !ok {
err = data.ErrUnknownConfigurationKey
}
return
}

View File

@ -1,20 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package config
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestNewSystemConfig(t *testing.T) {
s := NewSystem()
assert.NotNil(t, s)
}
func TestSystemConfig(t *testing.T) {
s := NewSystem()
assert.NotNil(t, s)
assert.True(t, s.Has("GOARCH"))
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -1,24 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package data
import (
"errors"
)
var (
ErrConfigUndefinedName = errors.New("Config block is missing a defined name")
)
type Block interface {
Identifier
ConfigurationType() TypeName
Loader
Validator
NewConfiguration(uri *string) error
ConfigurationValueGetter
Configuration() Configuration
Clone() Block
}

View File

@ -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
}

View File

@ -1,29 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package data
import (
"errors"
)
var (
ErrUnknownConfigurationType = errors.New("Unknown configuration type")
ErrUnknownConfigurationKey = errors.New("Unknown configuration key")
)
type ConfigurationValueGetter interface {
GetValue(key string) (any, error)
}
type ConfigurationValueChecker interface {
Has(key string) bool
}
type Configuration interface {
Identifier
Type() string
Reader
ConfigurationValueGetter
ConfigurationValueChecker
Clone() Configuration
}

View File

@ -1,40 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package data
import (
"errors"
)
var (
ErrUnsupportedConversion = errors.New("Unsupported conversion")
)
// Convert a resource to a document and a document to a resource
type Emitter interface {
Emit(document Document, filter ElementSelector) (Resource, error)
}
type Extractor interface {
Extract(resource Resource, filter ElementSelector) (Document, error)
}
type Converter interface {
Typer
Emitter
Extractor
Close() error
}
type ManyExtractor interface {
ExtractMany(resource Resource, filter ElementSelector) ([]Document, error)
}
type ManyEmitter interface {
EmitMany(documents []Document, filter ElementSelector) (Resource, error)
}
type DirectoryConverter interface {
SetRelative(flag bool)
}

View File

@ -1,38 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package data
import (
"context"
)
type Validator interface {
Validate() error
}
type Creator interface {
Create(context.Context) error
}
type Reader interface {
Read(context.Context) ([]byte, error)
}
type Updater interface {
Update(context.Context) error
}
type Deleter interface {
Delete(context.Context) error
}
type Info interface {
ReadStat() error
}
type Crudder interface {
Creator
Reader
Updater
Deleter
}

View File

@ -1,76 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package data
import (
"context"
"errors"
"decl/internal/codec"
"io"
"decl/internal/mapper"
)
var (
ErrEmptyDocument error = errors.New("Document contains no resources")
)
type Serializer interface {
JSON() ([]byte, error)
YAML() ([]byte, error)
PB() ([]byte, error)
Generate(w io.Writer) (error)
}
type Loader interface {
LoadString(string, codec.Format) (error)
Load([]byte, codec.Format) (error)
LoadReader(io.ReadCloser, codec.Format) (error)
}
type DocumentGetter interface {
GetDocument() Document
}
type DocumentStateTransformer interface {
Apply(overrideState string) error
}
type Document interface {
GetURI() string
Serializer
Loader
Validator
mapper.Mapper
NewResource(uri string) (Resource, error)
NewResourceFromParsedURI(uri URIParser) (Resource, error)
AddDeclaration(Declaration)
AddResourceDeclaration(resourceType string, resourceDeclaration Resource)
Types() (TypesRegistry[Resource])
// Resources() []Declaration
SetConfig(config Document)
ConfigDoc() Document
HasConfig(string) bool
GetConfig(string) Block
Apply(state string) error
Len() int
ResolveIds(ctx context.Context)
Filter(filter DeclarationSelector) []Declaration
Declarations() []Declaration
CheckConstraints() bool
Failures() int
ImportedDocuments() []Document
ConfigFilter(filter BlockSelector) []Block
AppendConfigurations([]Document)
Diff(with Document, output io.Writer) (returnOutput string, diffErr error)
DiffState(output io.Writer) (returnOutput string, diffErr error)
Clone() Document
AddError(error)
}

View File

@ -1,50 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package data
import (
"errors"
"net/url"
"decl/internal/transport"
)
var (
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 {
URI() string
SetParsedURI(URIParser) error
}
type DocumentElement interface {
Identifier
}
type Selector[Item comparable] func(r Item) bool
type ResourceSelector Selector[Resource]
type DeclarationSelector Selector[Declaration]
type BlockSelector Selector[Block]
type ElementSelector Selector[DocumentElement]

View File

@ -1,122 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package data
import (
"context"
"errors"
"decl/internal/mapper"
"decl/internal/transport"
"gitea.rosskeen.house/rosskeen.house/machine"
"io"
"io/fs"
)
var (
ErrInvalidResource error = errors.New("Invalid resource")
)
type ResourceMapper mapper.Map[string, Declaration]
type StateTransformer interface {
Apply() error
}
// Used by the resource factory to initialize new resources.
type ResourceInitializer interface {
Init(uri URIParser) error
}
type Resource interface {
Identifier
Type() string
StateMachine() machine.Stater
UseConfig(config ConfigurationValueGetter)
ResolveId(context.Context) string
Loader
StateTransformer
Crudder
Validator
Clone() Resource
SetResourceMapper(ResourceMapper)
}
type Declaration interface {
Identifier
ResourceType() TypeName
ResolveId(context.Context) string
Loader
Validator
DocumentStateTransformer
Resource() Resource
Clone() Declaration
}
func NewResourceMapper() ResourceMapper {
return mapper.New[string, Declaration]()
}
type ContentHasher interface {
Hash() []byte
HashHexString() string
}
type ContentIdentifier interface {
ContentType() string
}
type ContentReader interface {
ContentReaderStream() (*transport.Reader, error)
}
type ContentWriter interface {
ContentWriterStream() (*transport.Writer, error)
}
type ContentReadWriter interface {
ContentReader
ContentWriter
}
type ContentGetter interface {
GetContent(w io.Writer) (contentReader io.ReadCloser, err error)
}
type ContentSetter interface {
SetContent(r io.Reader) error
}
type ContentGetSetter interface {
ContentGetter
ContentSetter
}
type FileResource interface {
SetBasePath(int)
FilePath() string
SetFileInfo(fs.FileInfo) error
FileInfo() fs.FileInfo
ContentGetSetter
GetContentSourceRef() string
SetContentSourceRef(uri string)
SetFS(fs.FS)
PathNormalization(bool)
NormalizePath() error
GetTarget() string
SetGzipContent(bool)
}
type ExecResource interface {
Start() error
Wait() error
StdoutPipe() (io.ReadCloser, error)
StderrPipe() (io.ReadCloser, error)
}
type Signed interface {
Signature() Signature
}
type FileInfoGetter interface {
Stat() (fs.FileInfo, error)
}

View File

@ -1,12 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package data
import (
)
type Signature interface {
Verify(ContentHasher) error
SetHexString(string) error
String() string
}

View File

@ -1,50 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package data
import (
"gitea.rosskeen.house/rosskeen.house/machine"
)
func StorageMachine(sub machine.Subscriber) machine.Stater {
// start_destroy -> absent -> start_create -> present -> start_destroy
stater := machine.New("unknown")
stater.AddStates("initialized", "unkonwn", "absent", "start_create", "present", "start_delete", "start_read", "start_update")
stater.AddTransition("construct", machine.States("unknown"), "initialized")
stater.AddTransition("create", machine.States("unknown", "initialized", "absent"), "start_create")
if e := stater.AddSubscription("create", sub); e != nil {
return nil
}
stater.AddTransition("created", machine.States("start_create"), "present")
if e := stater.AddSubscription("created", sub); e != nil {
return nil
}
stater.AddTransition("exists", machine.States("unknown", "initialized", "absent"), "present")
if e := stater.AddSubscription("exists", sub); e != nil {
return nil
}
stater.AddTransition("notexists", machine.States("*"), "absent")
if e := stater.AddSubscription("notexists", sub); e != nil {
return nil
}
stater.AddTransition("read", machine.States("*"), "start_read")
if e := stater.AddSubscription("read", sub); e != nil {
return nil
}
stater.AddTransition("state_read", machine.States("start_read"), "present")
stater.AddTransition("update", machine.States("*"), "start_update")
if e := stater.AddSubscription("update", sub); e != nil {
return nil
}
stater.AddTransition("updated", machine.States("start_update"), "present")
stater.AddTransition("delete", machine.States("*"), "start_delete")
if e := stater.AddSubscription("delete", sub); e != nil {
return nil
}
stater.AddTransition("deleted", machine.States("start_delete"), "absent")
if e := stater.AddSubscription("deleted", sub); e != nil {
return nil
}
return stater
}

View File

@ -1,25 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package data
import (
"net/url"
)
type Factory[Product comparable] func(*url.URL) Product
type TypesRegistry[Product comparable] interface {
New(uri string) (result Product, err error)
NewFromParsedURI(uri *url.URL) (result Product, err error)
NewFromType(typename string) (result Product, err error)
Has(typename string) bool
//Get(string) Factory[Product]
}
type TypeName string //`json:"type"`
func (t TypeName) String() string { return string(t) }
type Typer interface {
Type() TypeName
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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"))
}

View File

@ -1,31 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package ext
import (
"io"
)
type Closer func() error
func WriteAddCloser(w io.WriteCloser, c Closer) io.WriteCloser {
a := writeAddCloser{ WriteCloser: w, AddClose: func() (err error) {
if err = w.Close(); err != nil {
return
}
if c != nil {
return c()
}
return
} }
return a
}
type writeAddCloser struct {
io.WriteCloser
AddClose Closer
}
func (w writeAddCloser) Close() error {
return w.AddClose()
}

View File

@ -1,19 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package ext
import (
"testing"
"github.com/stretchr/testify/assert"
"strings"
_ "fmt"
_ "log"
)
func TestNewWriteAddCloser(t *testing.T) {
var testWriter strings.Builder
closer := WriteAddCloser(WriteNopCloser(&testWriter), func() error { testWriter.Write([]byte("foo")); return nil })
closer.Close()
assert.Equal(t, "foo", testWriter.String())
}

View File

@ -1,25 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package ext
import (
"io"
)
// Restrict the underlying io.Reader to only exposed the io.Reader interface.
// Removes the io.WriterTo interface.
func NewReadCloser(r io.ReadCloser) io.ReadCloser {
return basicReadCloser{r}
}
type basicReadCloser struct {
io.ReadCloser
}
func NewReader(r io.Reader) io.Reader {
return basicReader{r}
}
type basicReader struct {
io.Reader
}

View File

@ -1,30 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package ext
import (
"testing"
"github.com/stretchr/testify/assert"
"strings"
_ "fmt"
_ "log"
"io"
)
func TestNewBasicReader(t *testing.T) {
testReader := strings.NewReader("some test data")
basicReader := NewReader(testReader)
assert.NotNil(t, basicReader)
_, ok := basicReader.(io.WriterTo)
assert.False(t, ok)
}
func TestNewBasicReadCloser(t *testing.T) {
testReader := strings.NewReader("some test data")
basicReader := NewReadCloser(io.NopCloser(testReader))
assert.NotNil(t, basicReader)
_, ok := basicReader.(io.WriterTo)
assert.False(t, ok)
_, hasCloser := basicReader.(io.Closer)
assert.True(t, hasCloser)
}

View File

@ -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)
}

View File

@ -1,17 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package ext
import (
"io"
)
func WriteNopCloser(w io.Writer) io.WriteCloser {
return writeNopCloser{w}
}
type writeNopCloser struct {
io.Writer
}
func (writeNopCloser) Close() error { return nil }

View File

@ -1,21 +0,0 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package ext
import (
"testing"
"github.com/stretchr/testify/assert"
"strings"
_ "fmt"
_ "log"
)
func TestNewWriteNopCloser(t *testing.T) {
var testWriter strings.Builder
closer := WriteNopCloser(&testWriter)
assert.NotNil(t, closer)
_, err := closer.Write([]byte("test data"))
assert.Nil(t, err)
assert.Equal(t, "test data", testWriter.String())
assert.Nil(t, closer.Close())
}

Some files were not shown because too many files have changed in this diff Show More