Compare commits
8 Commits
eaac8c8800
...
8e85305595
Author | SHA1 | Date | |
---|---|---|---|
8e85305595 | |||
d305305fab | |||
00d12be3f5 | |||
2d4234c6a0 | |||
fb544a455c | |||
1e32bc7129 | |||
59f66af792 | |||
24a18c3094 |
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
134
internal/config/openpgpkeyring.go
Normal file
134
internal/config/openpgpkeyring.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
|
||||
"io"
|
||||
"strings"
|
||||
"strconv"
|
||||
"gopkg.in/yaml.v3"
|
||||
"decl/internal/data"
|
||||
"decl/internal/codec"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
type OpenPGPKeyRing struct {
|
||||
Keyring string `json:"keyring,omitempty" yaml:"keyring,omitempty"`
|
||||
entities openpgp.EntityList
|
||||
}
|
||||
|
||||
func NewOpenPGPKeyRing() *OpenPGPKeyRing {
|
||||
o := &OpenPGPKeyRing{}
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) URI() string {
|
||||
return fmt.Sprintf("%s://%s", o.Type(), "")
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) SetURI(uri string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) SetParsedURI(uri data.URIParser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) Read(ctx context.Context) (yamlData []byte, err error) {
|
||||
pemReader := io.NopCloser(strings.NewReader(o.Keyring))
|
||||
o.entities, err = openpgp.ReadArmoredKeyRing(pemReader)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) Load(r io.Reader) (err error) {
|
||||
err = codec.NewYAMLDecoder(r).Decode(o)
|
||||
if err == nil {
|
||||
_, err = o.Read(context.Background())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) LoadYAML(yamlData string) (err error) {
|
||||
err = codec.NewYAMLStringDecoder(yamlData).Decode(o)
|
||||
slog.Info("OpenPGP.LoadYAML()", "keyring", len(o.Keyring), "err", err)
|
||||
if err == nil {
|
||||
_, err = o.Read(context.Background())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) UnmarshalJSON(data []byte) error {
|
||||
if unmarshalErr := json.Unmarshal(data, o); unmarshalErr != nil {
|
||||
return unmarshalErr
|
||||
}
|
||||
//o.NewReadConfigCommand()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) UnmarshalYAML(value *yaml.Node) error {
|
||||
type decodeOpenPGP OpenPGPKeyRing
|
||||
if unmarshalErr := value.Decode((*decodeOpenPGP)(o)); unmarshalErr != nil {
|
||||
return unmarshalErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) Clone() data.Configuration {
|
||||
jsonGeneric, _ := json.Marshal(o)
|
||||
clone := NewOpenPGPKeyRing()
|
||||
if unmarshalErr := json.Unmarshal(jsonGeneric, &clone); unmarshalErr != nil {
|
||||
panic(unmarshalErr)
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) Type() string {
|
||||
return "openpgp"
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) GetEntityIndex(key string) (index int, field string, err error) {
|
||||
values := strings.SplitN(key, ".", 2)
|
||||
if len(values) == 2 {
|
||||
if index, err = strconv.Atoi(values[0]); err == nil {
|
||||
field = values[1]
|
||||
}
|
||||
} else {
|
||||
err = data.ErrUnknownConfigurationKey
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) GetValue(name string) (result any, err error) {
|
||||
index, field, err := o.GetEntityIndex(name)
|
||||
if len(o.entities) > index && err == nil {
|
||||
switch field {
|
||||
case "PublicKey":
|
||||
return o.entities[index].PrimaryKey, err
|
||||
case "PrivateKey":
|
||||
return o.entities[index].PrimaryKey, err
|
||||
}
|
||||
}
|
||||
err = data.ErrUnknownConfigurationKey
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Expected key: 0.PrivateKey
|
||||
func (o *OpenPGPKeyRing) Has(key string) (ok bool) {
|
||||
index, field, err := o.GetEntityIndex(key)
|
||||
if len(o.entities) > index && err == nil {
|
||||
switch field {
|
||||
case "PublicKey":
|
||||
ok = o.entities[index].PrimaryKey != nil
|
||||
case "PrivateKey":
|
||||
ok = o.entities[index].PrimaryKey != nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
131
internal/config/openpgpkeyring_test.go
Normal file
131
internal/config/openpgpkeyring_test.go
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
privateKey = `
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
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
|
||||
=BAfD
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
`
|
||||
publicKey = `
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBGhI/VUBCADDVSm3mKY5JsncMMJFV0zELMrmip7dkK3vvMvVWVmMHiC4akDH
|
||||
WPxUdWNQPjE2e5HGF9Ebg0c7gu634VG470MnTzFdPV6f5zA+yJfdKrLq7gpl9QGW
|
||||
jFLIeK/l4xc+MIpOE1rD9WqYUUw2IYY8YANYq4yB36rq41VuZps/adI9Go5IhfcU
|
||||
3SVb7o7pa/gWE0FVu9ze31j2agC8FIKHgB++7bmYgbAQz5Qi1qgtG0Kn25QUacJ6
|
||||
Akm2+h4w3SQCR6HLRV2BO29x9mFBszf2KQ7DW2VNiGyUuQQ3m8v2ZidG/11ff6U6
|
||||
ad5tvr/8sYr5jOnKEJyDP9v9yQ04cU94GmsPABEBAAHNL1Rlc3RVc2VyMSAoVGVz
|
||||
dFVzZXIxKSA8dGVzdHVzZXJAcm9zc2tlZW4uaG91c2U+wsCKBBMBCAA+BQJoSP1V
|
||||
CRDfB5I2FtNl1xYhBADOnA8Aah3FXyl/m98HkjYW02XXAhsDAh4BAhkBAgsHAhUI
|
||||
AxYAAgMnBwIAAGKLB/9whwgfLkoTYI+Q0Te2uZXl7FPyW0t4tbUSoI1aiVw+ymND
|
||||
V+wuqYsjYDob2MyK/TAFkSCqZhBIRbGLRJtwzQwkF/amGuelHSSBX3LdxK/sp2UU
|
||||
+zv/NBEP1LlUNKchuxpdBPYjKHdbOLPoKqFfdCujSxpWTIeKCwvnDtuP+PlsjUgc
|
||||
afxMx+cVwe77AZ1Fi5CBD9Dr7nNsLovywrSiszyVh5eT0pbcu8Elf5oYQNT1q3ms
|
||||
aqrw95shlGQiwVgrfTvRCDeZnKafHwMUoh48otsNI9aS0HpChi+JNd+PLe/Wlsj5
|
||||
x+q0sHc+5/24zQvN0Wx9fPE4/7z8Pr+CUhAxjI7hzsBNBGhI/VUBCACfahoi2GRx
|
||||
OfOqJ4ZN8Te+4bJeOQdzTofAisfCU736q+MvEKtAuY72vzWdERWpn7XABmtkfII8
|
||||
Xyya8iHKuEGCETy0YoZ15GLBgSHhLnlN2U/4BkrUjGvUjZWquW22gfFB1q8ZdSGw
|
||||
pg8hrspY/b+nnAGdawq+hfE6YRQTmG5tkBrctxfxglBnKzezmL1a3arTHWf2SRlL
|
||||
eAWNI4sACryP/5plD6+9Z9YXTKeDlXnga6BzB80y7F2g0cPdtX20dWAGx4irq2I2
|
||||
SxlABvWMZrdm2Upr6yLHD2zTpbf1FkKFUw9j0lrL1fMq0/j9zbKJsTh6CtOlF04s
|
||||
AeoEV9SZ36GvABEBAAHCwHYEGAEIACoFAmhI/VUJEN8HkjYW02XXFiEEAM6cDwBq
|
||||
HcVfKX+b3weSNhbTZdcCGwwAAFxWB/9Af7qpYKldDz5iNYQluFoPfyHAVlOpiakf
|
||||
hvnu7hijTEgB1frByKu2m+k4m7gANBgfWldsYi1feWBVn6fspXuCc0k7opmQgol7
|
||||
ueTrsGnNB7U0l14HcKSTLZ2c/Jt7ZS6aXXjxpYPq0/5lt8+5sRkXxHI9HhAx6gK/
|
||||
RJCNITRt/V/gT4YWWbz8VHgkPIfHFsjr8brVhm4V/+JKZAG4eSNQp4QrTpdIjdfa
|
||||
1rd91qffyrWhPNTQErE2jFOXSU/OojdDjGJ7pwr0qj4FHACVL3/oqYog2NrUWf92
|
||||
QcwwWrEZNZUpJEMUB5q+tOoZ40ll0A8lsvABULZDqWveYpRQF+rQ
|
||||
=jBvZ
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
`
|
||||
)
|
||||
|
||||
func TestNewOpenPGPKeyRingConfig(t *testing.T) {
|
||||
p := NewOpenPGPKeyRing()
|
||||
assert.NotNil(t, p)
|
||||
}
|
||||
|
||||
func TestNewOpenPGPKeyRingConfigYAML(t *testing.T) {
|
||||
p := NewOpenPGPKeyRing()
|
||||
assert.NotNil(t, p)
|
||||
|
||||
config := fmt.Sprintf(`
|
||||
keyring: |-
|
||||
%s
|
||||
%s
|
||||
`, publicKey, privateKey)
|
||||
|
||||
yamlErr := p.LoadYAML(config)
|
||||
assert.Nil(t, yamlErr)
|
||||
|
||||
/*
|
||||
crt, err := p.GetValue("catemplate")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{"RKH"}, crt.(*x509.Certificate).Subject.Organization)
|
||||
*/
|
||||
|
||||
}
|
135
internal/config/openpgpsignature.go
Normal file
135
internal/config/openpgpsignature.go
Normal file
@ -0,0 +1,135 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
|
||||
"io"
|
||||
"strings"
|
||||
"strconv"
|
||||
"gopkg.in/yaml.v3"
|
||||
"decl/internal/data"
|
||||
"decl/internal/codec"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
type OpenPGPSignature struct {
|
||||
KeyRing string `json:"keyring,omitempty" yaml:"keyring,omitempty"`
|
||||
Signature string `json:"signature,omitempty" yaml:"signature,omitempty"`
|
||||
entities openpgp.EntityList
|
||||
}
|
||||
|
||||
func NewOpenPGPSignature() *OpenPGPSignature {
|
||||
o := &OpenPGPSignature{}
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) URI() string {
|
||||
return fmt.Sprintf("%s://%s", o.Type(), "")
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) SetURI(uri string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) SetParsedURI(uri data.URIParser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) Read(ctx context.Context) (yamlData []byte, err error) {
|
||||
pemReader := io.NopCloser(strings.NewReader(o.KeyRing))
|
||||
o.entities, err = openpgp.ReadArmoredKeyRing(pemReader)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) Load(r io.Reader) (err error) {
|
||||
err = codec.NewYAMLDecoder(r).Decode(o)
|
||||
if err == nil {
|
||||
_, err = o.Read(context.Background())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) LoadYAML(yamlData string) (err error) {
|
||||
err = codec.NewYAMLStringDecoder(yamlData).Decode(o)
|
||||
slog.Info("OpenPGP.LoadYAML()", "keyring", len(o.KeyRing), "err", err)
|
||||
if err == nil {
|
||||
_, err = o.Read(context.Background())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) UnmarshalJSON(data []byte) error {
|
||||
if unmarshalErr := json.Unmarshal(data, o); unmarshalErr != nil {
|
||||
return unmarshalErr
|
||||
}
|
||||
//o.NewReadConfigCommand()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) UnmarshalYAML(value *yaml.Node) error {
|
||||
type decodeOpenPGP OpenPGPSignature
|
||||
if unmarshalErr := value.Decode((*decodeOpenPGP)(o)); unmarshalErr != nil {
|
||||
return unmarshalErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) Clone() data.Configuration {
|
||||
jsonGeneric, _ := json.Marshal(o)
|
||||
clone := NewOpenPGPSignature()
|
||||
if unmarshalErr := json.Unmarshal(jsonGeneric, &clone); unmarshalErr != nil {
|
||||
panic(unmarshalErr)
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) Type() string {
|
||||
return "openpgp"
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) GetEntityIndex(key string) (index int, field string, err error) {
|
||||
values := strings.SplitN(key, ".", 2)
|
||||
if len(values) == 2 {
|
||||
if index, err = strconv.Atoi(values[0]); err == nil {
|
||||
field = values[1]
|
||||
}
|
||||
} else {
|
||||
err = data.ErrUnknownConfigurationKey
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) GetValue(name string) (result any, err error) {
|
||||
index, field, err := o.GetEntityIndex(name)
|
||||
if len(o.entities) > index && err == nil {
|
||||
switch field {
|
||||
case "PublicKey":
|
||||
return o.entities[index].PrimaryKey, err
|
||||
case "PrivateKey":
|
||||
return o.entities[index].PrimaryKey, err
|
||||
}
|
||||
}
|
||||
err = data.ErrUnknownConfigurationKey
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Expected key: 0.PrivateKey
|
||||
func (o *OpenPGPSignature) Has(key string) (ok bool) {
|
||||
index, field, err := o.GetEntityIndex(key)
|
||||
if len(o.entities) > index && err == nil {
|
||||
switch field {
|
||||
case "PublicKey":
|
||||
ok = o.entities[index].PrimaryKey != nil
|
||||
case "PrivateKey":
|
||||
ok = o.entities[index].PrimaryKey != nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
33
internal/config/openpgpsignature_test.go
Normal file
33
internal/config/openpgpsignature_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func TestNewOpenPGPSignatureConfig(t *testing.T) {
|
||||
p := NewOpenPGPSignature()
|
||||
assert.NotNil(t, p)
|
||||
}
|
||||
|
||||
func TestNewOpenPGPSignatureConfigYAML(t *testing.T) {
|
||||
p := NewOpenPGPSignature()
|
||||
assert.NotNil(t, p)
|
||||
|
||||
|
||||
|
||||
config := fmt.Sprintf(`
|
||||
keyring: |-
|
||||
%s
|
||||
%s
|
||||
`, publicKey, privateKey)
|
||||
|
||||
yamlErr := p.LoadYAML(config)
|
||||
assert.Nil(t, yamlErr)
|
||||
|
||||
p.Read(context.Background())
|
||||
}
|
9
internal/config/schemas/openpgp.schema.json
Normal file
9
internal/config/schemas/openpgp.schema.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"$id": "openpgp.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "openpgp",
|
||||
"type": "object",
|
||||
"required": [ "path", "filetype" ],
|
||||
"properties": {
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ func NewSystem() *System {
|
||||
s["importpath"] = []string {
|
||||
"/etc/jx/lib",
|
||||
}
|
||||
s["keyringpath"] = "/etc/jx/pgp/keyring.asc"
|
||||
return &s
|
||||
}
|
||||
|
||||
|
13
internal/data/originator.go
Normal file
13
internal/data/originator.go
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
)
|
||||
|
||||
var (
|
||||
)
|
||||
|
||||
type Originator interface {
|
||||
GetContentReadWriter() ContentReadWriter
|
||||
}
|
@ -38,7 +38,6 @@ type Resource interface {
|
||||
Crudder
|
||||
Validator
|
||||
Clone() Resource
|
||||
SetResourceMapper(ResourceMapper)
|
||||
}
|
||||
|
||||
type Declaration interface {
|
||||
|
@ -149,12 +149,16 @@ func (j *JxFile) Extract(resourceSource data.Resource, filter data.ElementSelect
|
||||
}
|
||||
|
||||
uri := resourceSource.URI()
|
||||
if folio.DocumentRegistry.HasDocument(folio.URI(uri)) {
|
||||
uri = fmt.Sprintf("%s?index=%d", uri, j.index)
|
||||
documentIndexUri := fmt.Sprintf("%s?index=%d", uri, j.index)
|
||||
|
||||
doc = folio.DocumentRegistry.NewDocument(folio.URI(documentIndexUri))
|
||||
if ! folio.DocumentRegistry.HasDocument(folio.URI(uri)) {
|
||||
folio.DocumentRegistry.SetDocument(folio.URI(uri), doc.(*folio.Document))
|
||||
doc.(*folio.Document).SetURI(uri)
|
||||
}
|
||||
doc = folio.DocumentRegistry.NewDocument(folio.URI(uri))
|
||||
|
||||
err = j.decoder.Decode(doc)
|
||||
slog.Info("JxFile.Extract()", "doc", doc, "jxfile", j, "error", err)
|
||||
slog.Info("JxFile.Extract()", "uri", uri, "doc", doc, "jxfile", j, "error", err)
|
||||
j.index++
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -73,7 +73,7 @@ func (d *Declaration) SetDocument(newDocument *Document) {
|
||||
d.document = newDocument
|
||||
d.SetConfig(d.document.config)
|
||||
d.ResourceTypes = d.document.Types()
|
||||
d.Attributes.SetResourceMapper(d.document.uris)
|
||||
d.Attributes.(ResourceMapSetter).SetResourceMapper(d.document.uris)
|
||||
}
|
||||
|
||||
func (d *Declaration) ResolveId(ctx context.Context) string {
|
||||
@ -209,6 +209,11 @@ func (d *Declaration) Apply(stateTransition string) (result error) {
|
||||
return result
|
||||
}
|
||||
result = stater.Trigger("read")
|
||||
case "restart": // XXX should only work for a process type resource
|
||||
if result = stater.Trigger("restart"); result != nil {
|
||||
return result
|
||||
}
|
||||
result = stater.Trigger("read")
|
||||
default:
|
||||
return fmt.Errorf("%w: %s on %s", ErrUnknownStateTransition, stateTransition, d.Attributes.URI())
|
||||
case "create", "present":
|
||||
@ -249,7 +254,7 @@ func (d *Declaration) SetConfig(configDoc data.Document) {
|
||||
return
|
||||
}
|
||||
if d.Config != "" { // XXX
|
||||
panic(fmt.Sprintf("failed setting config: %s", d.Config))
|
||||
panic(fmt.Errorf("%w: failed setting config: %s", data.ErrConfigUndefined, d.Config))
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,6 +401,10 @@ func (d *Declaration) UnmarshalJSON(jsonData []byte) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Declaration) GetContentReadWriter() data.ContentReadWriter {
|
||||
return d.Resource().(data.ContentReadWriter)
|
||||
}
|
||||
|
||||
/*
|
||||
func (l *LuaWorker) Receive(m message.Envelope) {
|
||||
s := m.Sender()
|
||||
|
@ -37,7 +37,7 @@ type Document struct {
|
||||
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
||||
Imports []URI `json:"imports,omitempty" yaml:"imports,omitempty"`
|
||||
Errors []string `json:"error,omitempty" yaml:"error,omitempty"`
|
||||
uris mapper.Store[string, data.Declaration]
|
||||
uris mapper.Store[URI, *Declaration]
|
||||
ResourceDeclarations []*Declaration `json:"resources,omitempty" yaml:"resources,omitempty"`
|
||||
configNames mapper.Store[string, data.Block] `json:"-" yaml:"-"`
|
||||
Configurations []*Block `json:"configurations,omitempty" yaml:"configurations,omitempty"`
|
||||
@ -57,7 +57,7 @@ func NewDocument(r *Registry) *Document {
|
||||
return &Document{
|
||||
Registry: r,
|
||||
Format: codec.FormatYaml,
|
||||
uris: mapper.New[string, data.Declaration](),
|
||||
uris: mapper.New[URI, *Declaration](),
|
||||
configNames: mapper.New[string, data.Block](),
|
||||
importPaths: NewSearchPath(configImportPath.GetStringSlice()),
|
||||
}
|
||||
@ -111,24 +111,24 @@ func (d *Document) Filter(filter data.DeclarationSelector) []data.Declaration {
|
||||
}
|
||||
|
||||
func (d *Document) Has(key string) bool {
|
||||
return d.uris.Has(key)
|
||||
return d.uris.Has(URI(key))
|
||||
}
|
||||
|
||||
func (d *Document) Get(key string) (any, bool) {
|
||||
return d.uris.Get(key)
|
||||
return d.uris.Get(URI(key))
|
||||
}
|
||||
|
||||
func (d *Document) Set(key string, value any) {
|
||||
d.uris.Set(key, value.(data.Declaration))
|
||||
d.uris.Set(URI(key), value.(*Declaration))
|
||||
}
|
||||
|
||||
func (d *Document) Delete(key string) {
|
||||
d.uris.Delete(key)
|
||||
d.uris.Delete(URI(key))
|
||||
}
|
||||
|
||||
func (d *Document) GetResource(uri string) *Declaration {
|
||||
if decl, ok := d.uris[uri]; ok {
|
||||
return decl.(*Declaration)
|
||||
if decl, ok := d.uris[URI(uri)]; ok {
|
||||
return decl
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -236,17 +236,17 @@ func (d *Document) GetSchemaFiles() (schemaFs fs.FS) {
|
||||
return
|
||||
}
|
||||
schemaFs, _ = d.Registry.Schemas.Get(d.Registry.DefaultSchema)
|
||||
slog.Info("Document.GetSchemaFiles()", "schemaFs", schemaFs)
|
||||
slog.Info("Document.GetSchemaFiles() default schema", "schema", d.Registry.DefaultSchema, "schemaFs", schemaFs)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Document) Validate() error {
|
||||
jsonDocument, jsonErr := d.JSON()
|
||||
slog.Info("Document.Validate() json", "err", jsonErr)
|
||||
slog.Info("Document.Validate() convert to json", "err", jsonErr)
|
||||
if jsonErr == nil {
|
||||
s := schema.New("document", d.GetSchemaFiles())
|
||||
err := s.Validate(string(jsonDocument))
|
||||
slog.Info("Document.Validate()", "error", err)
|
||||
slog.Info("Document.Validate() validate schema", "error", err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -356,12 +356,12 @@ func (d *Document) MapConfigurationURI(uri string, block data.Block) {
|
||||
}
|
||||
*/
|
||||
|
||||
func (d *Document) MapResourceURI(uri string, declaration data.Declaration) {
|
||||
d.uris[uri] = declaration
|
||||
func (d *Document) MapResourceURI(uri string, declaration *Declaration) {
|
||||
d.uris[URI(uri)] = declaration
|
||||
}
|
||||
|
||||
func (d *Document) UnMapResourceURI(uri string) {
|
||||
d.uris.Delete(uri)
|
||||
d.uris.Delete(URI(uri))
|
||||
}
|
||||
|
||||
func (d *Document) AddDeclaration(declaration data.Declaration) {
|
||||
@ -371,7 +371,7 @@ func (d *Document) AddDeclaration(declaration data.Declaration) {
|
||||
|
||||
d.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
||||
|
||||
d.MapResourceURI(uri, declaration)
|
||||
d.MapResourceURI(uri, decl)
|
||||
decl.SetDocument(d)
|
||||
d.Registry.DeclarationMap[decl] = d
|
||||
}
|
||||
@ -597,22 +597,38 @@ func (d *Document) Diff(with data.Document, output io.Writer) (returnOutput stri
|
||||
/*
|
||||
func (d *Document) UnmarshalValue(value *DocumentType) error {
|
||||
d.Requires = value.Requires
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
func (d *Document) UnmarshalLoadDependencies() (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s - %w", r, err)
|
||||
} else {
|
||||
err = fmt.Errorf("%s", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
err = d.loadImports()
|
||||
d.assignConfigurationsDocument()
|
||||
d.assignResourcesDocument()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Document) UnmarshalYAML(value *yaml.Node) (err error) {
|
||||
type decodeDocument Document
|
||||
/*
|
||||
t := &DocumentType{}
|
||||
if unmarshalDocumentErr := value.Decode(t); unmarshalDocumentErr != nil {
|
||||
return unmarshalDocumentErr
|
||||
}
|
||||
|
||||
*/
|
||||
if unmarshalResourcesErr := value.Decode((*decodeDocument)(d)); unmarshalResourcesErr != nil {
|
||||
return unmarshalResourcesErr
|
||||
}
|
||||
err = d.loadImports()
|
||||
d.assignConfigurationsDocument()
|
||||
d.assignResourcesDocument()
|
||||
err = d.UnmarshalLoadDependencies()
|
||||
return
|
||||
}
|
||||
|
||||
@ -622,12 +638,39 @@ func (d *Document) UnmarshalJSON(data []byte) (err error) {
|
||||
if unmarshalDocumentErr := json.Unmarshal(data, t); unmarshalDocumentErr != nil {
|
||||
return unmarshalDocumentErr
|
||||
}
|
||||
err = d.loadImports()
|
||||
d.assignConfigurationsDocument()
|
||||
d.assignResourcesDocument()
|
||||
err = d.UnmarshalLoadDependencies()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Document) AddError(e error) {
|
||||
d.Errors = append(d.Errors, e.Error())
|
||||
}
|
||||
|
||||
func (d *Document) GetContentReadWriter() data.ContentReadWriter {
|
||||
return d.URI
|
||||
}
|
||||
|
||||
func (d *Document) GetContent(w io.Writer) (contentReader io.ReadCloser, err error) {
|
||||
slog.Info("Document.GetContent()")
|
||||
|
||||
var buf strings.Builder
|
||||
err = codec.FormatYaml.Serialize(d, &buf)
|
||||
|
||||
contentReader = io.NopCloser(strings.NewReader(buf.String()))
|
||||
|
||||
if w != nil {
|
||||
copyBuffer := make([]byte, 32 * 1024)
|
||||
_, writeErr := io.CopyBuffer(w, contentReader, copyBuffer)
|
||||
if writeErr != nil {
|
||||
return nil, fmt.Errorf("File.GetContent(): CopyBuffer failed %v %v: %w", w, contentReader, writeErr)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Document) SetContent(r io.Reader) error {
|
||||
return d.LoadReader(io.NopCloser(r), codec.FormatYaml)
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ _ "gopkg.in/yaml.v3"
|
||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||
"decl/internal/codec"
|
||||
"decl/internal/data"
|
||||
"decl/internal/transport"
|
||||
"io"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
@ -89,9 +90,10 @@ func NewMockResource(typename string, stater machine.Stater) (m *MockResource) {
|
||||
InjectUpdate: func(context.Context) error { return nil },
|
||||
InjectDelete: func(context.Context) error { return nil },
|
||||
InjectUseConfig: func(data.ConfigurationValueGetter) {},
|
||||
InjectSetResourceMapper: func(data.ResourceMapper) {},
|
||||
InjectSetResourceMapper: func(ResourceMapper) {},
|
||||
InjectURI: func() string { return fmt.Sprintf("%s://bar", typename) },
|
||||
InjectNotify: func(*machine.EventMessage) {},
|
||||
InjectContentReaderStream: func() (*transport.Reader, error) { return nil, nil },
|
||||
}
|
||||
m.InjectInit = func(u data.URIParser) error {
|
||||
if u != nil {
|
||||
|
@ -11,6 +11,7 @@ _ "fmt"
|
||||
"decl/internal/data"
|
||||
"decl/internal/codec"
|
||||
"io"
|
||||
"decl/internal/transport"
|
||||
)
|
||||
|
||||
type MockResource struct {
|
||||
@ -34,9 +35,11 @@ type MockResource struct {
|
||||
InjectUpdate func(context.Context) error `json:"-" yaml:"-"`
|
||||
InjectDelete func(context.Context) error `json:"-" yaml:"-"`
|
||||
InjectStateMachine func() machine.Stater `json:"-" yaml:"-"`
|
||||
InjectSetResourceMapper func(data.ResourceMapper) `json:"-" yaml:"-"`
|
||||
InjectSetResourceMapper func(ResourceMapper) `json:"-" yaml:"-"`
|
||||
InjectUseConfig func(data.ConfigurationValueGetter) `json:"-" yaml:"-"`
|
||||
InjectNotify func(*machine.EventMessage) `json:"-" yaml:"-"`
|
||||
InjectContentReaderStream func() (*transport.Reader, error) `json:"-" yaml:"-"`
|
||||
InjectContentWriterStream func() (*transport.Writer, error) `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
func (m *MockResource) Clone() data.Resource {
|
||||
@ -67,7 +70,7 @@ func (m *MockResource) ResolveId(ctx context.Context) string {
|
||||
return m.InjectResolveId(ctx)
|
||||
}
|
||||
|
||||
func (m *MockResource) SetResourceMapper(rm data.ResourceMapper) {
|
||||
func (m *MockResource) SetResourceMapper(rm ResourceMapper) {
|
||||
m.InjectSetResourceMapper(rm)
|
||||
}
|
||||
|
||||
@ -139,6 +142,14 @@ func (m *MockResource) Notify(em *machine.EventMessage) {
|
||||
m.InjectNotify(em)
|
||||
}
|
||||
|
||||
func (m *MockResource) ContentReaderStream() (*transport.Reader, error) {
|
||||
return m.InjectContentReaderStream()
|
||||
}
|
||||
|
||||
func (m *MockResource) ContentWriterStream() (*transport.Writer, error) {
|
||||
return m.InjectContentWriterStream()
|
||||
}
|
||||
|
||||
func (m *MockResource) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, m); err != nil {
|
||||
return err
|
||||
|
229
internal/folio/ref.go
Normal file
229
internal/folio/ref.go
Normal file
@ -0,0 +1,229 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package folio
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gopkg.in/yaml.v3"
|
||||
"decl/internal/mapper"
|
||||
"decl/internal/data"
|
||||
"decl/internal/transport"
|
||||
"net/url"
|
||||
"log/slog"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRefMapperMismatch = errors.New("Ref type does not the provided mapper")
|
||||
)
|
||||
|
||||
type Ref struct {
|
||||
Uri URI `json:"uri" yaml:"uri"`
|
||||
RefType ReferenceType `json:"type,omitempty" yaml:"type,omitempty"`
|
||||
documentMapper mapper.Store[URI, *Document]
|
||||
resourceMapper mapper.Store[URI, *Declaration]
|
||||
}
|
||||
|
||||
type decodeRef Ref
|
||||
|
||||
func NewRef() *Ref {
|
||||
return &Ref {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReferenceType) UnmarshalValue(value string) error {
|
||||
switch value {
|
||||
case string(ReferenceTypeResource), string(ReferenceTypeDocument):
|
||||
*r = ReferenceType(value)
|
||||
default:
|
||||
*r = ReferenceTypeResource
|
||||
//return ErrInvalidReferenceType
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReferenceType) UnmarshalJSON(jsonData []byte) error {
|
||||
var s string
|
||||
if unmarshalReferenceTypeErr := json.Unmarshal(jsonData, &s); unmarshalReferenceTypeErr != nil {
|
||||
return unmarshalReferenceTypeErr
|
||||
}
|
||||
return r.UnmarshalValue(s)
|
||||
}
|
||||
|
||||
func (r *ReferenceType) UnmarshalYAML(value *yaml.Node) error {
|
||||
var s string
|
||||
if err := value.Decode(&s); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.UnmarshalValue(s)
|
||||
}
|
||||
|
||||
func (r *Ref) UnmarshalValue(value *decodeRef) error {
|
||||
slog.Info("Ref.UnmarshalValue", "decode", value)
|
||||
r.Uri = value.Uri
|
||||
switch value.RefType {
|
||||
case ReferenceTypeResource:
|
||||
r.RefType = value.RefType
|
||||
case ReferenceTypeDocument:
|
||||
r.RefType = value.RefType
|
||||
r.SetMapper(DocumentRegistry.UriMap)
|
||||
default:
|
||||
r.RefType = ReferenceTypeResource
|
||||
//return ErrInvalidReferenceType
|
||||
}
|
||||
slog.Info("Ref.UnmarshalValue", "stored", *r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Ref) UnmarshalJSON(jsonData []byte) error {
|
||||
decodeJsonToRef := &decodeRef{}
|
||||
if unmarshalReferenceTypeErr := json.Unmarshal(jsonData, decodeJsonToRef); unmarshalReferenceTypeErr != nil {
|
||||
return unmarshalReferenceTypeErr
|
||||
}
|
||||
return r.UnmarshalValue(decodeJsonToRef)
|
||||
}
|
||||
|
||||
func (r *Ref) UnmarshalYAML(value *yaml.Node) error {
|
||||
decodeYamlToRef := &decodeRef{}
|
||||
if err := value.Decode(decodeYamlToRef); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.UnmarshalValue(decodeYamlToRef)
|
||||
}
|
||||
|
||||
func (r *Ref) SetMapper(m any) error {
|
||||
var ok bool
|
||||
switch r.RefType {
|
||||
case ReferenceTypeResource:
|
||||
if r.resourceMapper, ok = m.(mapper.Store[URI, *Declaration]); ! ok {
|
||||
return fmt.Errorf("%w - %T is not a %s", ErrRefMapperMismatch, m, r.RefType)
|
||||
}
|
||||
case ReferenceTypeDocument:
|
||||
if r.documentMapper, ok = m.(mapper.Store[URI, *Document]); ! ok {
|
||||
return fmt.Errorf("%w - %T is not a %s", ErrRefMapperMismatch, m, r.RefType)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Dereference[T ReferenceTypes](uri URI, look mapper.Map[URI, T]) T {
|
||||
if uri != "" && look != nil {
|
||||
if v, ok := look.Get(uri); ok {
|
||||
slog.Info("Ref::Dereference()", "value", v, "mapper", look)
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Ref) Lookup(look any) ContentReadWriter {
|
||||
slog.Info("Ref.Lookup()", "ref", r, "mapper", look)
|
||||
switch r.RefType {
|
||||
case ReferenceTypeResource:
|
||||
if resourceDeclaration := Dereference(r.Uri, look.(mapper.Store[URI, *Declaration])); resourceDeclaration != nil {
|
||||
return resourceDeclaration.GetContentReadWriter()
|
||||
}
|
||||
case ReferenceTypeDocument:
|
||||
if document := Dereference(r.Uri, look.(mapper.Store[URI, *Document])); document != nil {
|
||||
return document.GetContentReadWriter()
|
||||
}
|
||||
}
|
||||
return r.Uri
|
||||
}
|
||||
|
||||
func (r *Ref) Dereference(look any) any {
|
||||
switch r.RefType {
|
||||
case ReferenceTypeResource:
|
||||
if look == nil {
|
||||
return Dereference(r.Uri, r.resourceMapper)
|
||||
} else {
|
||||
return Dereference(r.Uri, look.(mapper.Store[URI, *Declaration]))
|
||||
}
|
||||
case ReferenceTypeDocument:
|
||||
if look == nil {
|
||||
return Dereference(r.Uri, r.documentMapper)
|
||||
} else {
|
||||
return Dereference(r.Uri, look.(mapper.Store[URI, *Document]))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Ref) DereferenceDefault() any {
|
||||
switch r.RefType {
|
||||
case ReferenceTypeResource:
|
||||
return Dereference(r.Uri, r.resourceMapper)
|
||||
case ReferenceTypeDocument:
|
||||
return Dereference(r.Uri, r.documentMapper)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Ref) Parse() data.URIParser {
|
||||
return r.Uri.Parse()
|
||||
}
|
||||
|
||||
func (r *Ref) Exists() bool {
|
||||
return r.Uri.Exists()
|
||||
}
|
||||
|
||||
func (r *Ref) ContentReaderStream() (*transport.Reader, error) {
|
||||
return r.Uri.ContentReaderStream()
|
||||
}
|
||||
|
||||
func (r *Ref) ContentWriterStream() (*transport.Writer, error) {
|
||||
return r.Uri.ContentWriterStream()
|
||||
}
|
||||
|
||||
func (r *Ref) GetContent(w io.Writer) (contentReader io.ReadCloser, err error) {
|
||||
target := r.DereferenceDefault()
|
||||
if targetContent, ok := target.(data.ContentGetSetter); ok {
|
||||
return targetContent.GetContent(w)
|
||||
}
|
||||
return nil, fmt.Errorf("Ref target does not support ContentGetSetter: %s, %s, %#v", r.RefType, r.Uri, target)
|
||||
}
|
||||
|
||||
func (r *Ref) SetContent(contentReader io.Reader) error {
|
||||
target := r.DereferenceDefault()
|
||||
if targetContent, ok := target.(data.ContentGetSetter); ok {
|
||||
return targetContent.SetContent(contentReader)
|
||||
}
|
||||
return fmt.Errorf("Ref target does not support ContentGetSetter: %s, %s, %#v", r.RefType, r.Uri, target)
|
||||
}
|
||||
|
||||
func (r *Ref) Reader() (reader io.ReadCloser, err error) {
|
||||
switch r.RefType {
|
||||
case ReferenceTypeResource:
|
||||
return r.ContentReaderStream()
|
||||
case ReferenceTypeDocument:
|
||||
reader, err = r.GetContent(nil)
|
||||
if reader == nil {
|
||||
return r.ContentReaderStream()
|
||||
}
|
||||
return
|
||||
}
|
||||
return nil, ErrInvalidReferenceType
|
||||
}
|
||||
|
||||
func (r *Ref) String() string {
|
||||
return r.Uri.String()
|
||||
}
|
||||
|
||||
func (r *Ref) SetURL(url *url.URL) {
|
||||
r.Uri.SetURL(url)
|
||||
}
|
||||
|
||||
func (r *Ref) Extension() (string, string) {
|
||||
return r.Uri.Extension()
|
||||
}
|
||||
|
||||
func (r *Ref) IsEmpty() bool {
|
||||
return r.Uri.IsEmpty()
|
||||
}
|
||||
|
||||
func (r *Ref) FindIn(s *SearchPath) {
|
||||
r.Uri.FindIn(s)
|
||||
}
|
67
internal/folio/ref_test.go
Normal file
67
internal/folio/ref_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package folio
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"fmt"
|
||||
_ "decl/internal/data"
|
||||
"decl/internal/mapper"
|
||||
"decl/internal/codec"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"io"
|
||||
)
|
||||
|
||||
func TestReference(t *testing.T) {
|
||||
f := NewFooResource()
|
||||
resourceMapper := mapper.New[URI, *Declaration]()
|
||||
f.Name = string(TempDir)
|
||||
f.Size = 10
|
||||
f.MockResource.InjectURI = func() string { return fmt.Sprintf("%s://%s", "foo", f.Name) }
|
||||
d := NewDeclaration()
|
||||
d.Type = "foo"
|
||||
d.Attributes = f
|
||||
resourceMapper[URI(d.URI())] = d
|
||||
slog.Info("TestReference", "declaration", d, "mapper", resourceMapper)
|
||||
|
||||
var fooRef *Ref = NewRef()
|
||||
fooRef.RefType = ReferenceTypeResource
|
||||
fooRef.Uri = URI(fmt.Sprintf("foo://%s", string(TempDir)))
|
||||
u := fooRef.Uri.Parse().URL()
|
||||
assert.Equal(t, "foo", u.Scheme)
|
||||
assert.True(t, fooRef.Uri.Exists())
|
||||
|
||||
fromRef := fooRef.Lookup(resourceMapper)
|
||||
assert.NotNil(t, fromRef)
|
||||
}
|
||||
|
||||
func TestDocumentRef(t *testing.T) {
|
||||
docUri := fmt.Sprintf("file://%s/doc.yaml", TempDir)
|
||||
DocumentRegistry.ResourceTypes = TestResourceTypes
|
||||
document := `
|
||||
---
|
||||
resources:
|
||||
- type: foo
|
||||
attributes:
|
||||
name: "testfoo"
|
||||
size: 10022
|
||||
`
|
||||
TempDir.CreateFile("doc.yaml", document)
|
||||
|
||||
d := DocumentRegistry.NewDocument(URI(docUri))
|
||||
assert.NotNil(t, d)
|
||||
docReader := io.NopCloser(strings.NewReader(document))
|
||||
|
||||
e := d.LoadReader(docReader, codec.FormatYaml)
|
||||
assert.Nil(t, e)
|
||||
|
||||
var docRef *Ref = NewRef()
|
||||
docRef.RefType = ReferenceTypeDocument
|
||||
docRef.Uri = URI(docUri)
|
||||
u := docRef.Uri.Parse().URL()
|
||||
assert.Equal(t, "file", u.Scheme)
|
||||
|
||||
assert.Equal(t, d, docRef.Dereference(DocumentRegistry.UriMap).(*Document))
|
||||
}
|
133
internal/folio/referrer.go
Normal file
133
internal/folio/referrer.go
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package folio
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gopkg.in/yaml.v3"
|
||||
"net/url"
|
||||
"decl/internal/data"
|
||||
"decl/internal/mapper"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidURI error = errors.New("Invalid URI")
|
||||
ErrInvalidReferenceType error = errors.New("invalid ReferenceType value")
|
||||
)
|
||||
|
||||
type ReferenceTypes interface {
|
||||
*Document | *Declaration
|
||||
}
|
||||
|
||||
type RefMap[T ReferenceTypes] mapper.Store[string, T]
|
||||
|
||||
type ReferenceMapper[T ReferenceTypes] interface {
|
||||
mapper.Map[URI, T]
|
||||
}
|
||||
type ResourceMapper ReferenceMapper[*Declaration]
|
||||
type DocumentMapper ReferenceMapper[*Document]
|
||||
|
||||
type ReferenceType string
|
||||
type RefType[T ReferenceTypes, TypedReferenceMapper ReferenceMapper[T]] ReferenceType
|
||||
|
||||
type ReferenceURI[T ReferenceTypes] URI
|
||||
type DocumentURI ReferenceURI[*Document]
|
||||
type ResourceURI ReferenceURI[*Declaration]
|
||||
|
||||
const (
|
||||
ReferenceTypeResource ReferenceType = "resource"
|
||||
RefTypeResource RefType[*Declaration, mapper.Store[URI, *Declaration]] = RefType[*Declaration, mapper.Store[URI, *Declaration]](ReferenceTypeResource)
|
||||
ReferenceTypeDocument ReferenceType = "document"
|
||||
RefTypeDocument RefType[*Document, mapper.Store[URI, *Document]] = RefType[*Document, mapper.Store[URI, *Document]](ReferenceTypeDocument)
|
||||
)
|
||||
|
||||
type CommonReferrer interface {
|
||||
ReferenceType() ReferenceType
|
||||
Parse() *url.URL
|
||||
Exists() bool
|
||||
data.ContentReadWriter
|
||||
IsEmpty() bool
|
||||
}
|
||||
|
||||
// interface for a reference
|
||||
type Referrer[T ReferenceTypes, TypedReferenceMapper ReferenceMapper[T]] interface {
|
||||
CommonReferrer
|
||||
Lookup(TypedReferenceMapper) ContentReadWriter
|
||||
GetContentReadWriter(TypedReferenceMapper) ContentReadWriter
|
||||
Dereference(TypedReferenceMapper) T
|
||||
}
|
||||
|
||||
/*
|
||||
func (r ReferenceType) URIType() any {
|
||||
switch r {
|
||||
case ReferenceTypeDocument:
|
||||
return ReferenceURI[*Document]
|
||||
case ReferenceTypeResource:
|
||||
return ReferenceURI[*Declaration]
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func (r *RefType[T, TypedReferenceMapper]) UnmarshalValue(value string) error {
|
||||
switch value {
|
||||
case string(ReferenceTypeResource), string(ReferenceTypeDocument):
|
||||
*r = RefType[T, TypedReferenceMapper](value)
|
||||
return nil
|
||||
default:
|
||||
return ErrInvalidReferenceType
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RefType[T, TypedReferenceMapper]) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if unmarshalReferenceTypeErr := json.Unmarshal(data, &s); unmarshalReferenceTypeErr != nil {
|
||||
return unmarshalReferenceTypeErr
|
||||
}
|
||||
return r.UnmarshalValue(s)
|
||||
}
|
||||
|
||||
func (r *RefType[T, TypedReferenceMapper]) UnmarshalYAML(value *yaml.Node) error {
|
||||
var s string
|
||||
if err := value.Decode(&s); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.UnmarshalValue(s)
|
||||
}
|
||||
|
||||
func (r *RefType[T, TypedReferenceMapper]) Dereference(uri URI, look TypedReferenceMapper) (result T) {
|
||||
result = nil
|
||||
if uri != "" {
|
||||
if v,ok := look.Get(uri); ok {
|
||||
result = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RefURI[T ReferenceTypes, RT RefType[T, ReferenceMapper[T]], RU ReferenceURI[T]](uri URI, reftype RT) (refuri RU) {
|
||||
refuri = RU(uri)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
func (r *ReferenceURI[T]) GetContentReadWriter(mapper TypedReferenceMapper) ContentReadWriter {
|
||||
if URI(r).String() == "" {
|
||||
return nil
|
||||
}
|
||||
if mapper != nil {
|
||||
if v,ok := mapper.Get(string(r)); ok {
|
||||
return v.(data.Originator).GetContentReadWriter()
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
*/
|
||||
|
||||
type ResourceMapSetter interface {
|
||||
SetResourceMapper(ResourceMapper)
|
||||
}
|
||||
|
||||
func NewResourceMapper() ResourceMapper {
|
||||
return mapper.New[URI, *Declaration]()
|
||||
}
|
@ -60,7 +60,8 @@ func (r *Registry) HasDocument(key URI) bool {
|
||||
}
|
||||
|
||||
func (r *Registry) GetDocument(key URI) (*Document, bool) {
|
||||
return r.UriMap.Get(key)
|
||||
document, result := r.UriMap.Get(key)
|
||||
return document, result
|
||||
}
|
||||
|
||||
func (r *Registry) SetDocument(key URI, value *Document) {
|
||||
@ -72,7 +73,7 @@ func (r *Registry) NewDocument(uri URI) (doc *Document) {
|
||||
doc.SetURI(string(uri))
|
||||
r.Documents = append(r.Documents, doc)
|
||||
if uri != "" {
|
||||
r.UriMap[uri] = doc
|
||||
r.UriMap.Set(uri, doc)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ package folio
|
||||
import (
|
||||
"decl/internal/transport"
|
||||
"decl/internal/data"
|
||||
"decl/internal/mapper"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
@ -31,25 +32,26 @@ type ContentReadWriter interface {
|
||||
type ResourceReference URI
|
||||
|
||||
// Return a Content ReadWriter for the resource referred to.
|
||||
func (r ResourceReference) Lookup(look data.ResourceMapper) ContentReadWriter {
|
||||
func (r ResourceReference) Lookup(look mapper.Map[URI, *Declaration]) ContentReadWriter {
|
||||
if string(r) == "" {
|
||||
return nil
|
||||
}
|
||||
slog.Info("ResourceReference.Lookup()", "resourcereference", r, "resourcemapper", look)
|
||||
if look != nil {
|
||||
if v,ok := look.Get(string(r)); ok {
|
||||
if v,ok := look.Get(URI(r)); ok {
|
||||
slog.Info("ResourceReference.Lookup()", "resourcereference", r, "result", v.Resource())
|
||||
return v.Resource().(ContentReadWriter)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r ResourceReference) Dereference(look data.ResourceMapper) data.Resource {
|
||||
func (r ResourceReference) Dereference(look mapper.Map[URI, *Declaration]) data.Resource {
|
||||
slog.Info("ResourceReference.Dereference()", "resourcereference", r, "resourcemapper", look)
|
||||
if look != nil {
|
||||
if v,ok := look.Get(string(r)); ok {
|
||||
if v,ok := look.Get(URI(r)); ok {
|
||||
slog.Info("ResourceReference.Dereference()", "resourcereference", r, "result", v)
|
||||
return v.(*Declaration).Attributes
|
||||
return v.Attributes
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -74,3 +76,7 @@ func (r ResourceReference) ContentWriterStream() (*transport.Writer, error) {
|
||||
func (r ResourceReference) IsEmpty() bool {
|
||||
return URI(r).IsEmpty()
|
||||
}
|
||||
|
||||
func (r *ResourceReference) FindIn(s *SearchPath) {
|
||||
(*URI)(r).FindIn(s)
|
||||
}
|
||||
|
@ -6,19 +6,18 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"fmt"
|
||||
"decl/internal/data"
|
||||
"decl/internal/mapper"
|
||||
)
|
||||
|
||||
func TestResourceReference(t *testing.T) {
|
||||
f := NewFooResource()
|
||||
resourceMapper := mapper.New[string, data.Declaration]()
|
||||
resourceMapper := mapper.New[URI, *Declaration]()
|
||||
f.Name = string(TempDir)
|
||||
f.Size = 10
|
||||
d := NewDeclaration()
|
||||
d.Type = "foo"
|
||||
d.Attributes = f
|
||||
resourceMapper[d.URI()] = d
|
||||
resourceMapper[URI(d.URI())] = d
|
||||
|
||||
var foo ResourceReference = ResourceReference(fmt.Sprintf("foo://%s", string(TempDir)))
|
||||
u := foo.Parse()
|
||||
|
17
internal/folio/schemas/ref.schema.json
Normal file
17
internal/folio/schemas/ref.schema.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"$id": "ref.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ref",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Type of object referred to",
|
||||
"enum": [ "resource", "document" ]
|
||||
},
|
||||
"uri": {
|
||||
"type": "string",
|
||||
"description": "URI of the object referred to"
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"decl/internal/data"
|
||||
"decl/internal/folio"
|
||||
_ "decl/internal/mapper"
|
||||
"log/slog"
|
||||
"errors"
|
||||
)
|
||||
@ -31,7 +32,7 @@ type Common struct {
|
||||
|
||||
State string `json:"state,omitempty" yaml:"state,omitempty"`
|
||||
config data.ConfigurationValueGetter
|
||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||
Resources folio.ResourceMapper `json:"-" yaml:"-"`
|
||||
Errors []error `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
@ -53,7 +54,7 @@ func (c *Common) ContentType() string {
|
||||
return c.exttype
|
||||
}
|
||||
|
||||
func (c *Common) SetResourceMapper(resources data.ResourceMapper) {
|
||||
func (c *Common) SetResourceMapper(resources folio.ResourceMapper) {
|
||||
c.Resources = resources
|
||||
}
|
||||
|
||||
@ -158,6 +159,7 @@ func (c *Common) IsResourceInconsistent() (result bool) {
|
||||
|
||||
func (c *Common) AddError(err error) (error) {
|
||||
if err != nil {
|
||||
slog.Info("Common.AddError()", "errors", c.Errors, "err", err)
|
||||
c.Errors = append(c.Errors, err)
|
||||
}
|
||||
return err
|
||||
|
@ -32,15 +32,22 @@ _ "os/exec"
|
||||
"decl/internal/containerlog"
|
||||
"bytes"
|
||||
_ "encoding/base64"
|
||||
"time"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
ContainerTypeName TypeName = "container"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrContainerWaitTimeOut = errors.New("Container wait timed out waiting for state")
|
||||
)
|
||||
|
||||
type ContainerClient interface {
|
||||
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
|
||||
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error
|
||||
ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error
|
||||
ContainerList(context.Context, container.ListOptions) ([]types.Container, error)
|
||||
ContainerInspect(context.Context, string) (types.ContainerJSON, error)
|
||||
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
||||
@ -91,7 +98,6 @@ type Container struct {
|
||||
Stderr string `json:"stderr,omitempty" yaml:"stderr,omitempty"`
|
||||
|
||||
apiClient ContainerClient
|
||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -137,10 +143,6 @@ func (c *Container) SetParsedURI(u data.URIParser) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Container) SetResourceMapper(resources data.ResourceMapper) {
|
||||
c.Resources = resources
|
||||
}
|
||||
|
||||
func (c *Container) Clone() data.Resource {
|
||||
return &Container {
|
||||
Id: c.Id,
|
||||
@ -233,7 +235,6 @@ func (c *Container) Notify(m *machine.EventMessage) {
|
||||
panic(createErr)
|
||||
}
|
||||
case "start_delete":
|
||||
slog.Info("Container.Notify()", "event", "start_delete")
|
||||
if deleteErr := c.Delete(ctx); deleteErr == nil {
|
||||
if triggerErr := c.StateMachine().Trigger("deleted"); triggerErr == nil {
|
||||
return
|
||||
@ -245,6 +246,26 @@ func (c *Container) Notify(m *machine.EventMessage) {
|
||||
c.Common.State = "present"
|
||||
panic(deleteErr)
|
||||
}
|
||||
case "restarting":
|
||||
if restartErr := c.Restart(ctx); restartErr == nil {
|
||||
if triggerErr := c.StateMachine().Trigger("restarted"); triggerErr == nil {
|
||||
return
|
||||
} else {
|
||||
_ = c.AddError(triggerErr)
|
||||
}
|
||||
} else {
|
||||
_ = c.AddError(restartErr)
|
||||
if c.IsResourceInconsistent() {
|
||||
if triggerErr := c.StateMachine().Trigger("restart-failed"); triggerErr == nil {
|
||||
panic(restartErr)
|
||||
} else {
|
||||
_ = c.AddError(triggerErr)
|
||||
panic(fmt.Errorf("%w - %w", restartErr, triggerErr))
|
||||
}
|
||||
}
|
||||
_ = c.StateMachine().Trigger("notexists")
|
||||
panic(restartErr)
|
||||
}
|
||||
case "present", "created", "read":
|
||||
c.Common.State = "present"
|
||||
case "running":
|
||||
@ -475,10 +496,10 @@ func (c *Container) Read(ctx context.Context) ([]byte, error) {
|
||||
|
||||
func (c *Container) Inspect(ctx context.Context, containerID string) error {
|
||||
containerJSON, err := c.apiClient.ContainerInspect(ctx, containerID)
|
||||
if client.IsErrNotFound(err) {
|
||||
if client.IsErrNotFound(err) || containerJSON.State == nil {
|
||||
c.Common.State = "absent"
|
||||
} else {
|
||||
c.Common.State = "present"
|
||||
c.Common.State = containerJSON.State.Status
|
||||
c.Id = containerJSON.ID
|
||||
if c.Name == "" {
|
||||
if containerJSON.Name[0] == '/' {
|
||||
@ -583,3 +604,64 @@ func (c *Container) ResolveId(ctx context.Context) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type WaitCondition func(state *types.ContainerState) bool
|
||||
func (c *Container) wait(ctx context.Context, untilstate WaitCondition) error {
|
||||
statusCh := make(chan bool)
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 60 * time.Second)
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
if containerJSON, err := c.apiClient.ContainerInspect(ctx, c.Id); err == nil {
|
||||
statusCh <- untilstate(containerJSON.State)
|
||||
return
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-timeoutCtx.Done():
|
||||
return ErrContainerWaitTimeOut
|
||||
case <-statusCh:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) Restart(ctx context.Context) (err error) {
|
||||
if err = c.apiClient.ContainerRestart(ctx, c.Id, container.StopOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = c.wait(ctx, func(state *types.ContainerState) bool {
|
||||
return state.Running
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
/*
|
||||
waitTimeout := 60 * time.Second
|
||||
interval := 2 * time.Second
|
||||
deadline := time.Now().Add(waitTimeout)
|
||||
|
||||
for {
|
||||
if time.Now().After(deadline) {
|
||||
panic("")
|
||||
}
|
||||
if state.Running {
|
||||
if state.Health != nil {
|
||||
fmt.Println("Health status:", state.Health.Status)
|
||||
if state.Health.Status == "healthy" {
|
||||
fmt.Println("Container is healthy and running.")
|
||||
break
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Container is running (no health check defined).")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(interval)
|
||||
}
|
||||
*/
|
||||
return nil
|
||||
}
|
||||
|
@ -93,7 +93,6 @@ type ContainerImage struct {
|
||||
outputWriter strings.Builder `json:"-" yaml:"-"`
|
||||
|
||||
apiClient ContainerImageClient
|
||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||
contextDocument data.Document `json:"-" yaml:"-"`
|
||||
ConverterTypes data.TypesRegistry[data.Converter] `json:"-" yaml:"-"`
|
||||
|
||||
@ -193,10 +192,6 @@ func (c *ContainerImage) NormalizePath() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ContainerImage) SetResourceMapper(resources data.ResourceMapper) {
|
||||
c.Resources = resources
|
||||
}
|
||||
|
||||
func (c *ContainerImage) Clone() data.Resource {
|
||||
return &ContainerImage {
|
||||
Common: c.Common,
|
||||
|
@ -214,8 +214,8 @@ attributes:
|
||||
_, readErr := contextFile.Resource().Read(context.Background())
|
||||
assert.Nil(t, readErr)
|
||||
|
||||
c.Resources = data.NewResourceMapper()
|
||||
c.Resources.Set(contextDirUri, contextFile)
|
||||
c.Resources = folio.NewResourceMapper()
|
||||
c.Resources.Set(folio.URI(contextDirUri), contextFile)
|
||||
|
||||
d, contextErr := c.ContextDocument()
|
||||
assert.Nil(t, contextErr)
|
||||
|
@ -49,7 +49,6 @@ type ContainerNetwork struct {
|
||||
Created time.Time `json:"created" yaml:"created"`
|
||||
|
||||
apiClient ContainerNetworkClient
|
||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -92,10 +91,6 @@ func (n *ContainerNetwork) NormalizePath() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *ContainerNetwork) SetResourceMapper(resources data.ResourceMapper) {
|
||||
n.Resources = resources
|
||||
}
|
||||
|
||||
func (n *ContainerNetwork) Clone() data.Resource {
|
||||
return &ContainerNetwork {
|
||||
Common: n.Common.Clone(),
|
||||
|
@ -5,6 +5,7 @@ package resource
|
||||
import (
|
||||
"context"
|
||||
"decl/tests/mocks"
|
||||
"decl/internal/codec"
|
||||
_ "encoding/json"
|
||||
_ "fmt"
|
||||
"github.com/docker/docker/api/types"
|
||||
@ -20,6 +21,7 @@ _ "os"
|
||||
"strings"
|
||||
"testing"
|
||||
"bytes"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewContainerResource(t *testing.T) {
|
||||
@ -47,6 +49,9 @@ func TestReadContainer(t *testing.T) {
|
||||
ID: "123456789abc",
|
||||
Name: "test",
|
||||
Image: "alpine",
|
||||
State: &types.ContainerState{
|
||||
Status: "running",
|
||||
},
|
||||
}}, nil
|
||||
},
|
||||
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
||||
@ -62,18 +67,19 @@ func TestReadContainer(t *testing.T) {
|
||||
}
|
||||
|
||||
c := NewContainer(m)
|
||||
assert.NotEqual(t, nil, c)
|
||||
assert.NotNil(t, c)
|
||||
|
||||
e := c.LoadDecl(decl)
|
||||
assert.Equal(t, nil, e)
|
||||
assert.Nil(t, e)
|
||||
assert.Equal(t, "testcontainer", c.Name)
|
||||
|
||||
resourceYaml, readContainerErr := c.Read(ctx)
|
||||
assert.Equal(t, nil, readContainerErr)
|
||||
assert.Nil(t, readContainerErr)
|
||||
assert.Greater(t, len(resourceYaml), 0)
|
||||
assert.Equal(t, "running", c.State)
|
||||
}
|
||||
|
||||
func TestCreateContainer(t *testing.T) {
|
||||
func TestCreateDeleteContainer(t *testing.T) {
|
||||
m := &mocks.MockContainerClient{
|
||||
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
|
||||
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
||||
@ -148,3 +154,119 @@ func TestContainerLogOutput(t *testing.T) {
|
||||
assert.Equal(t, "done.", c.Stdout)
|
||||
|
||||
}
|
||||
|
||||
func TestWaitContainer(t *testing.T) {
|
||||
mockState := &types.ContainerState{
|
||||
Status: "",
|
||||
}
|
||||
m := &mocks.MockContainerClient{
|
||||
InjectContainerInspect: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
||||
return types.ContainerJSON{
|
||||
ContainerJSONBase: &types.ContainerJSONBase{
|
||||
ID: "abcdef012",
|
||||
Name: "testcontainer",
|
||||
Image: "alpine",
|
||||
State: mockState,
|
||||
}}, nil
|
||||
},
|
||||
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
|
||||
mockState.Status = "created"
|
||||
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
||||
},
|
||||
InjectContainerStop: func(context.Context, string, container.StopOptions) error {
|
||||
return nil
|
||||
},
|
||||
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
|
||||
return nil
|
||||
},
|
||||
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
||||
var res container.WaitResponse
|
||||
resChan := make(chan container.WaitResponse)
|
||||
errChan := make(chan error, 1)
|
||||
go func() { resChan <- res }()
|
||||
return resChan, errChan
|
||||
},
|
||||
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader("done.")), nil
|
||||
},
|
||||
}
|
||||
|
||||
c := NewContainer(m)
|
||||
|
||||
assert.Nil(t, c.LoadString(`
|
||||
name: "testcontainer"
|
||||
image: "alpine"
|
||||
state: present
|
||||
`, codec.FormatYaml))
|
||||
|
||||
assert.Nil(t, c.Apply())
|
||||
|
||||
assert.Equal(t, "testcontainer", c.Name)
|
||||
assert.Nil(t, c.wait(context.Background(), func(state *types.ContainerState) bool {
|
||||
return state.Status == "running"
|
||||
}))
|
||||
}
|
||||
|
||||
func TestRestartContainer(t *testing.T) {
|
||||
mockState := &types.ContainerState{
|
||||
Status: "",
|
||||
}
|
||||
m := &mocks.MockContainerClient{
|
||||
InjectContainerInspect: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
||||
return types.ContainerJSON{
|
||||
ContainerJSONBase: &types.ContainerJSONBase{
|
||||
ID: "abcdef012",
|
||||
Name: "testcontainer",
|
||||
Image: "alpine",
|
||||
State: mockState,
|
||||
}}, nil
|
||||
},
|
||||
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
|
||||
mockState.Status = "created"
|
||||
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
||||
},
|
||||
InjectContainerStop: func(context.Context, string, container.StopOptions) error {
|
||||
return nil
|
||||
},
|
||||
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
|
||||
return nil
|
||||
},
|
||||
InjectContainerRestart: func(ctx context.Context, containerID string, options container.StopOptions) error {
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
mockState.Status = "running"
|
||||
mockState.Running = true
|
||||
}()
|
||||
return nil
|
||||
},
|
||||
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
||||
var res container.WaitResponse
|
||||
resChan := make(chan container.WaitResponse)
|
||||
errChan := make(chan error, 1)
|
||||
go func() { resChan <- res }()
|
||||
return resChan, errChan
|
||||
},
|
||||
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader("done.")), nil
|
||||
},
|
||||
}
|
||||
|
||||
c := NewContainer(m)
|
||||
|
||||
assert.Nil(t, c.LoadString(`
|
||||
name: "testcontainer"
|
||||
image: "alpine"
|
||||
state: present
|
||||
`, codec.FormatYaml))
|
||||
|
||||
assert.Equal(t, "testcontainer", c.Name)
|
||||
|
||||
assert.Nil(t, c.Apply())
|
||||
|
||||
c.State = "present" // overwrite the state
|
||||
c.StateMachine().Trigger("restart")
|
||||
|
||||
assert.Equal(t, "running", c.State)
|
||||
|
||||
assert.Nil(t, c.Apply())
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ type ContainerVolume struct {
|
||||
volume.Volume `json:",inline" yaml:",inline"`
|
||||
|
||||
apiClient ContainerVolumeClient
|
||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -85,10 +84,6 @@ func (v *ContainerVolume) NormalizePath() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *ContainerVolume) SetResourceMapper(resources data.ResourceMapper) {
|
||||
v.Resources = resources
|
||||
}
|
||||
|
||||
func (v *ContainerVolume) Clone() data.Resource {
|
||||
return &ContainerVolume {
|
||||
Common: v.Common.Clone(),
|
||||
|
@ -34,8 +34,6 @@ type Exec struct {
|
||||
ReadTemplate *command.Command `yaml:"read,omitempty" json:"read,omitempty"`
|
||||
UpdateTemplate *command.Command `yaml:"update,omitempty" json:"update,omitempty"`
|
||||
DeleteTemplate *command.Command `yaml:"delete,omitempty" json:"delete,omitempty"`
|
||||
|
||||
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -63,10 +61,6 @@ func NewExec() *Exec {
|
||||
return e
|
||||
}
|
||||
|
||||
func (x *Exec) SetResourceMapper(resources data.ResourceMapper) {
|
||||
x.Resources = resources
|
||||
}
|
||||
|
||||
func (x *Exec) Clone() data.Resource {
|
||||
return &Exec {
|
||||
Common: x.Common.Clone(),
|
||||
|
@ -35,7 +35,16 @@ func TestExecApplyResourceTransformation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReadExec(t *testing.T) {
|
||||
|
||||
x := NewExec()
|
||||
decl := `
|
||||
read:
|
||||
path: ls
|
||||
args:
|
||||
- -al
|
||||
`
|
||||
assert.Nil(t, x.LoadDecl(decl))
|
||||
assert.Equal(t, "ls", x.ReadTemplate.Path)
|
||||
assert.Equal(t, command.CommandArg("-al"), x.ReadTemplate.Args[0])
|
||||
}
|
||||
|
||||
func TestReadExecError(t *testing.T) {
|
||||
|
@ -48,7 +48,6 @@ type Group struct {
|
||||
ReadCommand *command.Command `json:"-" yaml:"-"`
|
||||
UpdateCommand *command.Command `json:"-" yaml:"-"`
|
||||
DeleteCommand *command.Command `json:"-" yaml:"-"`
|
||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||
groupStatus *user.Group `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
@ -102,10 +101,6 @@ func (g *Group) NormalizePath() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Group) SetResourceMapper(resources data.ResourceMapper) {
|
||||
g.Resources = resources
|
||||
}
|
||||
|
||||
func (g *Group) Clone() data.Resource {
|
||||
newg := &Group {
|
||||
Common: g.Common,
|
||||
|
@ -91,7 +91,6 @@ type HTTP struct {
|
||||
LastModified time.Time `json:"lastmodified,omitempty" yaml:"lastmodified,omitempty"`
|
||||
Size int64 `yaml:"size,omitempty" json:"size,omitempty"`
|
||||
SignatureValue string `yaml:"signature,omitempty" json:"signature,omitempty"`
|
||||
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
||||
reader *transport.Reader `yaml:"-" json:"-"`
|
||||
writer *transport.ReadWriter `yaml:"-" json:"-"`
|
||||
}
|
||||
@ -127,10 +126,6 @@ func (h *HTTP) NormalizePath() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HTTP) SetResourceMapper(resources data.ResourceMapper) {
|
||||
h.Resources = resources
|
||||
}
|
||||
|
||||
func (h *HTTP) Open() (err error) {
|
||||
u := h.Common.parsedURI
|
||||
if u == nil {
|
||||
|
@ -136,8 +136,6 @@ type Iptable struct {
|
||||
ReadChainCommand *command.Command `yaml:"-" json:"-"`
|
||||
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
||||
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
||||
|
||||
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
||||
}
|
||||
|
||||
|
||||
@ -185,10 +183,6 @@ func (i *Iptable) Init(u data.URIParser) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (i *Iptable) SetResourceMapper(resources data.ResourceMapper) {
|
||||
i.Resources = resources
|
||||
}
|
||||
|
||||
func (i *Iptable) Clone() data.Resource {
|
||||
newIpt := &Iptable {
|
||||
Common: i.Common,
|
||||
@ -286,13 +280,13 @@ func (i *Iptable) Notify(m *machine.EventMessage) {
|
||||
// Set the chain ID and update the mapped URI
|
||||
func (i *Iptable) SetId(id uint) {
|
||||
if i.Id != id {
|
||||
uri := i.URI()
|
||||
uri := folio.URI(i.URI())
|
||||
i.Id = id
|
||||
decl, ok := i.Resources.Get(uri)
|
||||
if ok {
|
||||
i.Resources.Delete(uri)
|
||||
}
|
||||
i.Resources.Set(i.URI(), decl)
|
||||
i.Resources.Set(folio.URI(i.URI()), decl)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ _ "syscall"
|
||||
"testing"
|
||||
_ "time"
|
||||
"decl/internal/command"
|
||||
"decl/internal/data"
|
||||
"decl/internal/folio"
|
||||
)
|
||||
|
||||
func TestNewIptableResource(t *testing.T) {
|
||||
@ -167,7 +167,7 @@ func TestIptableRuleExtractorById(t *testing.T) {
|
||||
|
||||
func TestIptableRuleExtractorByFlags(t *testing.T) {
|
||||
ipt := NewIptable()
|
||||
ipt.Resources = data.NewResourceMapper()
|
||||
ipt.Resources = folio.NewResourceMapper()
|
||||
assert.NotNil(t, ipt)
|
||||
ipt.Table = IptableName("filter")
|
||||
ipt.Chain = IptableChain("FOO")
|
||||
|
34
internal/resource/mock_buffer_resource_test.go
Normal file
34
internal/resource/mock_buffer_resource_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "gopkg.in/yaml.v3"
|
||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||
"decl/internal/transport"
|
||||
"decl/internal/ext"
|
||||
"strings"
|
||||
"io"
|
||||
)
|
||||
|
||||
func NewMockBufferResource(id string, target *strings.Builder) *MockResource {
|
||||
return &MockResource {
|
||||
InjectType: func() string { return "buffer" },
|
||||
InjectResolveId: func(ctx context.Context) string { return id },
|
||||
InjectRead: func(ctx context.Context) ([]byte, error) { return nil,nil },
|
||||
InjectLoadDecl: func(string) error { return nil },
|
||||
InjectApply: func() error { return nil },
|
||||
InjectStateMachine: func() machine.Stater { return nil },
|
||||
InjectContentWriterStream: func() (*transport.Writer, error) {
|
||||
w := &transport.Writer{}
|
||||
w.SetStream(ext.WriteNopCloser(target))
|
||||
return w, nil
|
||||
},
|
||||
InjectContentReaderStream: func() (*transport.Reader, error) {
|
||||
r := &transport.Reader{}
|
||||
r.SetStream(io.NopCloser(strings.NewReader(target.String())))
|
||||
return r, nil
|
||||
},
|
||||
}
|
||||
}
|
@ -9,9 +9,13 @@ _ "gopkg.in/yaml.v3"
|
||||
_ "fmt"
|
||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||
"decl/internal/data"
|
||||
"decl/internal/codec"
|
||||
"decl/internal/transport"
|
||||
"io"
|
||||
)
|
||||
|
||||
type MockResource struct {
|
||||
*Common `json:",inline" yaml:",inline"`
|
||||
InjectURI func() string
|
||||
InjectType func() string
|
||||
InjectResolveId func(ctx context.Context) string
|
||||
@ -20,6 +24,9 @@ type MockResource struct {
|
||||
InjectApply func() error
|
||||
InjectRead func(context.Context) ([]byte, error)
|
||||
InjectStateMachine func() machine.Stater
|
||||
InjectContentType func() string
|
||||
InjectContentReaderStream func() (*transport.Reader, error)
|
||||
InjectContentWriterStream func() (*transport.Writer, error)
|
||||
}
|
||||
|
||||
func (m *MockResource) Clone() data.Resource {
|
||||
@ -68,3 +75,64 @@ func (m *MockResource) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
func (m *MockResource) SetParsedURI(URIParser) error {
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
||||
func (m *MockResource) UseConfig(config data.ConfigurationValueGetter) {
|
||||
|
||||
}
|
||||
|
||||
func (m *MockResource) LoadString(string, codec.Format) (error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockResource) Load([]byte, codec.Format) (error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockResource) LoadReader(io.ReadCloser, codec.Format) (error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockResource) Create(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockResource) Update(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockResource) Delete(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockResource) ReadStat() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockResource) ContentType() string {
|
||||
if m.InjectContentType == nil {
|
||||
return ""
|
||||
}
|
||||
return m.InjectContentType()
|
||||
}
|
||||
|
||||
func (m *MockResource) ContentReaderStream() (*transport.Reader, error) {
|
||||
return m.InjectContentReaderStream()
|
||||
}
|
||||
|
||||
func (m *MockResource) ContentWriterStream() (*transport.Writer, error) {
|
||||
return m.InjectContentWriterStream()
|
||||
}
|
||||
|
||||
func (m *MockResource) GetContent(w io.Writer) (contentReader io.ReadCloser, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockResource) SetContent(r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -96,8 +96,6 @@ type NetworkRoute struct {
|
||||
ReadCommand *command.Command `yaml:"-" json:"-"`
|
||||
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
||||
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
||||
|
||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
func NewNetworkRoute() *NetworkRoute {
|
||||
@ -119,10 +117,6 @@ func (n *NetworkRoute) NormalizePath() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NetworkRoute) SetResourceMapper(resources data.ResourceMapper) {
|
||||
n.Resources = resources
|
||||
}
|
||||
|
||||
func (n *NetworkRoute) Clone() data.Resource {
|
||||
newn := &NetworkRoute {
|
||||
Common: n.Common,
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
|
||||
var (
|
||||
ErrOpenPGPEncryptionFailure error = errors.New("OpenPGP encryption failure")
|
||||
ErrOpenPGPDecryptionFailure error = errors.New("OpenPGP decryption failure")
|
||||
)
|
||||
|
||||
const (
|
||||
@ -65,6 +66,7 @@ func NewOpenPGPKeyRing() (o *OpenPGPKeyRing) {
|
||||
Common: NewCommon(OpenPGPKeyRingTypeName, false),
|
||||
Bits: 2048,
|
||||
}
|
||||
o.Common.NormalizePath = o.NormalizePath
|
||||
return
|
||||
}
|
||||
|
||||
@ -81,7 +83,6 @@ func (o *OpenPGPKeyRing) NormalizePath() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func (o *OpenPGPKeyRing) Validate() (err error) {
|
||||
var keyringJson []byte
|
||||
if keyringJson, err = o.JSON(); err == nil {
|
||||
@ -124,8 +125,29 @@ func (o *OpenPGPKeyRing) EncryptPrivateKey(entity *openpgp.Entity) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) DecryptPrivateKey(entity *openpgp.Entity) error {
|
||||
if o.config != nil && entity.PrivateKey != nil {
|
||||
passphraseConfig, _ := o.config.GetValue("passphrase")
|
||||
passphrase := []byte(passphraseConfig.(string))
|
||||
if len(passphrase) > 0 {
|
||||
if decryptErr := entity.PrivateKey.Decrypt(passphrase); decryptErr != nil {
|
||||
return fmt.Errorf("%w private key: %w", ErrOpenPGPDecryptionFailure, decryptErr)
|
||||
}
|
||||
for _, subkey := range entity.Subkeys {
|
||||
if decryptErr := subkey.PrivateKey.Encrypt(passphrase); decryptErr != nil {
|
||||
return fmt.Errorf("%w subkey (private key): %w", ErrOpenPGPDecryptionFailure, decryptErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) Create(ctx context.Context) (err error) {
|
||||
var entity *openpgp.Entity
|
||||
var keyRingFileStream io.WriteCloser
|
||||
var keyRingBuffer bytes.Buffer
|
||||
|
||||
cfg := o.Config()
|
||||
entity, err = openpgp.NewEntity(o.Name, o.Comment, o.Email, cfg)
|
||||
o.entityList = append(o.entityList, entity)
|
||||
@ -142,25 +164,37 @@ func (o *OpenPGPKeyRing) Create(ctx context.Context) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(o.KeyRing) == 0 {
|
||||
var keyringBuffer bytes.Buffer
|
||||
if publicKeyWriter, err := armor.Encode(&keyringBuffer, openpgp.PublicKeyType, nil); err == nil {
|
||||
if err = entity.Serialize(publicKeyWriter); err == nil {
|
||||
|
||||
}
|
||||
publicKeyWriter.Close()
|
||||
}
|
||||
keyringBuffer.WriteString("\n")
|
||||
if privateKeyWriter, err := armor.Encode(&keyringBuffer, openpgp.PrivateKeyType, nil); err == nil {
|
||||
if len(o.KeyRing) == 0 { // XXX this should probably always overwrite the value of KeyRing
|
||||
if privateKeyWriter, err := armor.Encode(&keyRingBuffer, openpgp.PrivateKeyType, nil); err == nil {
|
||||
if err = entity.SerializePrivateWithoutSigning(privateKeyWriter, nil); err == nil {
|
||||
|
||||
} else {
|
||||
slog.Error("Failed writing privatekey", "err", err)
|
||||
}
|
||||
privateKeyWriter.Close()
|
||||
keyRingBuffer.WriteString("\n")
|
||||
}
|
||||
keyringBuffer.WriteString("\n")
|
||||
if publicKeyWriter, err := armor.Encode(&keyRingBuffer, openpgp.PublicKeyType, nil); err == nil {
|
||||
if err = entity.Serialize(publicKeyWriter); err == nil {
|
||||
|
||||
o.KeyRing = keyringBuffer.String()
|
||||
} else {
|
||||
slog.Error("Failed writing public key", "err", err)
|
||||
}
|
||||
publicKeyWriter.Close()
|
||||
keyRingBuffer.WriteString("\n")
|
||||
}
|
||||
|
||||
o.KeyRing = keyRingBuffer.String()
|
||||
}
|
||||
|
||||
if len(o.KeyRingRef) > 0 {
|
||||
keyRingFileStream, _ = o.KeyRingRef.Lookup(o.Resources).ContentWriterStream()
|
||||
defer keyRingFileStream.Close()
|
||||
if _, writeErr := keyRingFileStream.Write([]byte(o.KeyRing)); writeErr != nil {
|
||||
err = writeErr
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -333,6 +367,8 @@ func (o *OpenPGPKeyRing) ResolveId(ctx context.Context) string {
|
||||
if e := o.NormalizePath(); e != nil {
|
||||
panic(e)
|
||||
}
|
||||
o.GetIdentityFromKeyRing()
|
||||
o.Common.Path = fmt.Sprintf("%s/%s/%s", o.Name, o.Comment, o.Email)
|
||||
return o.Common.Path
|
||||
}
|
||||
|
||||
@ -363,6 +399,33 @@ func (o *OpenPGPKeyRing) ReadStat() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) GetIdentityFromKeyRing() error {
|
||||
if len(o.KeyRing) > 0 {
|
||||
if keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(o.KeyRing))); err != nil {
|
||||
return err
|
||||
} else {
|
||||
for _, entity := range keyring {
|
||||
for identityName, identity := range entity.Identities {
|
||||
slog.Info("OpenPGPKeyRing.GetIdentityFromKeyRing()", "identity", identityName)
|
||||
if identity.UserId != nil {
|
||||
if len(identity.UserId.Name) > 0 {
|
||||
o.Name = identity.UserId.Name
|
||||
}
|
||||
if len(identity.UserId.Comment) > 0 {
|
||||
o.Comment = identity.UserId.Comment
|
||||
}
|
||||
if len(identity.UserId.Email) > 0 {
|
||||
o.Email = identity.UserId.Email
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) Read(ctx context.Context) (yamlData []byte, err error) {
|
||||
var keyringReader io.ReadCloser
|
||||
statErr := o.ReadStat()
|
||||
@ -371,11 +434,19 @@ func (o *OpenPGPKeyRing) Read(ctx context.Context) (yamlData []byte, err error)
|
||||
}
|
||||
|
||||
if keyringReader, err = o.GetContent(nil); err == nil {
|
||||
defer keyringReader.Close()
|
||||
if krData, krErr := io.ReadAll(keyringReader); krErr == nil {
|
||||
o.KeyRing = string(krData)
|
||||
o.entityList, err = openpgp.ReadArmoredKeyRing(strings.NewReader(o.KeyRing))
|
||||
} else {
|
||||
err = krErr
|
||||
return nil, fmt.Errorf("%w - %w: %s", ErrResourceStateAbsent, krErr, o.Path)
|
||||
}
|
||||
}
|
||||
o.GetIdentityFromKeyRing()
|
||||
|
||||
for i := range o.entityList {
|
||||
if decryptErr := o.DecryptPrivateKey(o.entityList[i]); decryptErr != nil {
|
||||
return nil, fmt.Errorf("%w - %w: %s", ErrResourceStateAbsent, decryptErr, o.Path)
|
||||
}
|
||||
}
|
||||
|
||||
@ -408,19 +479,24 @@ func (o *OpenPGPKeyRing) readThru() (contentReader io.ReadCloser, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) URI() string { return string(o.Common.URI()) }
|
||||
func (o *OpenPGPKeyRing) URI() string {
|
||||
return fmt.Sprintf("%s://%s/%s/%s", o.Type(), o.Name, o.Comment, o.Email)
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) Type() string { return "openpgp-keyring" }
|
||||
func (o *OpenPGPKeyRing) Type() string { return string(OpenPGPKeyRingTypeName) }
|
||||
|
||||
func (o *OpenPGPKeyRing) ContentReaderStream() (*transport.Reader, error) {
|
||||
if len(o.KeyRing) == 0 && ! o.KeyRingRef.IsEmpty() {
|
||||
//if len(o.KeyRing) == 0 && ! o.KeyRingRef.IsEmpty() {
|
||||
if ! o.KeyRingRef.IsEmpty() {
|
||||
slog.Info("OpenPGPKeyRing.ContentReaderStream()", "keyring", o)
|
||||
return o.KeyRingRef.Lookup(nil).ContentReaderStream()
|
||||
}
|
||||
return nil, fmt.Errorf("Cannot provide transport reader for string content")
|
||||
}
|
||||
|
||||
func (o *OpenPGPKeyRing) ContentWriterStream() (*transport.Writer, error) {
|
||||
if len(o.KeyRing) == 0 && ! o.KeyRingRef.IsEmpty() {
|
||||
//if len(o.KeyRing) == 0 && ! o.KeyRingRef.IsEmpty() {
|
||||
if ! o.KeyRingRef.IsEmpty() {
|
||||
return o.KeyRingRef.Lookup(nil).ContentWriterStream()
|
||||
}
|
||||
return nil, fmt.Errorf("Cannot provide transport writer for string content")
|
||||
|
@ -8,6 +8,112 @@ import (
|
||||
"testing"
|
||||
"fmt"
|
||||
"decl/internal/data"
|
||||
"decl/internal/codec"
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
"bytes"
|
||||
"log/slog"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
var(
|
||||
TestUserKeyPublic string = `
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBGhI/VUBCADDVSm3mKY5JsncMMJFV0zELMrmip7dkK3vvMvVWVmMHiC4akDH
|
||||
WPxUdWNQPjE2e5HGF9Ebg0c7gu634VG470MnTzFdPV6f5zA+yJfdKrLq7gpl9QGW
|
||||
jFLIeK/l4xc+MIpOE1rD9WqYUUw2IYY8YANYq4yB36rq41VuZps/adI9Go5IhfcU
|
||||
3SVb7o7pa/gWE0FVu9ze31j2agC8FIKHgB++7bmYgbAQz5Qi1qgtG0Kn25QUacJ6
|
||||
Akm2+h4w3SQCR6HLRV2BO29x9mFBszf2KQ7DW2VNiGyUuQQ3m8v2ZidG/11ff6U6
|
||||
ad5tvr/8sYr5jOnKEJyDP9v9yQ04cU94GmsPABEBAAHNL1Rlc3RVc2VyMSAoVGVz
|
||||
dFVzZXIxKSA8dGVzdHVzZXJAcm9zc2tlZW4uaG91c2U+wsCKBBMBCAA+BQJoSP1V
|
||||
CRDfB5I2FtNl1xYhBADOnA8Aah3FXyl/m98HkjYW02XXAhsDAh4BAhkBAgsHAhUI
|
||||
AxYAAgMnBwIAAGKLB/9whwgfLkoTYI+Q0Te2uZXl7FPyW0t4tbUSoI1aiVw+ymND
|
||||
V+wuqYsjYDob2MyK/TAFkSCqZhBIRbGLRJtwzQwkF/amGuelHSSBX3LdxK/sp2UU
|
||||
+zv/NBEP1LlUNKchuxpdBPYjKHdbOLPoKqFfdCujSxpWTIeKCwvnDtuP+PlsjUgc
|
||||
afxMx+cVwe77AZ1Fi5CBD9Dr7nNsLovywrSiszyVh5eT0pbcu8Elf5oYQNT1q3ms
|
||||
aqrw95shlGQiwVgrfTvRCDeZnKafHwMUoh48otsNI9aS0HpChi+JNd+PLe/Wlsj5
|
||||
x+q0sHc+5/24zQvN0Wx9fPE4/7z8Pr+CUhAxjI7hzsBNBGhI/VUBCACfahoi2GRx
|
||||
OfOqJ4ZN8Te+4bJeOQdzTofAisfCU736q+MvEKtAuY72vzWdERWpn7XABmtkfII8
|
||||
Xyya8iHKuEGCETy0YoZ15GLBgSHhLnlN2U/4BkrUjGvUjZWquW22gfFB1q8ZdSGw
|
||||
pg8hrspY/b+nnAGdawq+hfE6YRQTmG5tkBrctxfxglBnKzezmL1a3arTHWf2SRlL
|
||||
eAWNI4sACryP/5plD6+9Z9YXTKeDlXnga6BzB80y7F2g0cPdtX20dWAGx4irq2I2
|
||||
SxlABvWMZrdm2Upr6yLHD2zTpbf1FkKFUw9j0lrL1fMq0/j9zbKJsTh6CtOlF04s
|
||||
AeoEV9SZ36GvABEBAAHCwHYEGAEIACoFAmhI/VUJEN8HkjYW02XXFiEEAM6cDwBq
|
||||
HcVfKX+b3weSNhbTZdcCGwwAAFxWB/9Af7qpYKldDz5iNYQluFoPfyHAVlOpiakf
|
||||
hvnu7hijTEgB1frByKu2m+k4m7gANBgfWldsYi1feWBVn6fspXuCc0k7opmQgol7
|
||||
ueTrsGnNB7U0l14HcKSTLZ2c/Jt7ZS6aXXjxpYPq0/5lt8+5sRkXxHI9HhAx6gK/
|
||||
RJCNITRt/V/gT4YWWbz8VHgkPIfHFsjr8brVhm4V/+JKZAG4eSNQp4QrTpdIjdfa
|
||||
1rd91qffyrWhPNTQErE2jFOXSU/OojdDjGJ7pwr0qj4FHACVL3/oqYog2NrUWf92
|
||||
QcwwWrEZNZUpJEMUB5q+tOoZ40ll0A8lsvABULZDqWveYpRQF+rQ
|
||||
=jBvZ
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
`
|
||||
TestUserKeyPair string = `
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
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
|
||||
=BAfD
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
`
|
||||
)
|
||||
|
||||
func TestNewOpenPGPKeyRingResource(t *testing.T) {
|
||||
@ -106,3 +212,81 @@ func TestReadKeyRing(t *testing.T) {
|
||||
assert.Contains(t, testKeyRing.entityList[0].Identities, "TestUser (TestUser) <matthewrich.conf@gmail.com>")
|
||||
|
||||
}
|
||||
|
||||
func TestDecryptKeyPairRing(t *testing.T) {
|
||||
keyReader := bytes.NewBufferString(TestUserKeyPair)
|
||||
block, err := armor.Decode(keyReader)
|
||||
|
||||
assert.NotEqual(t, err, io.EOF)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, block.Type != openpgp.PublicKeyType && block.Type != openpgp.PrivateKeyType)
|
||||
|
||||
slog.Info("TestDecryptKeyPairRing", "block", block)
|
||||
|
||||
entityList, err := openpgp.ReadKeyRing(block.Body)
|
||||
assert.True(t, entityList[0].PrivateKey.Encrypted)
|
||||
|
||||
// entityList, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(TestUserKeyPair))
|
||||
slog.Info("TestDecryptKeyPairRing", "entity", entityList[0], "err", err)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, entityList)
|
||||
|
||||
entityList[0].PrivateKey.Decrypt([]byte("foo"))
|
||||
assert.False(t, entityList[0].PrivateKey.Encrypted)
|
||||
|
||||
/*
|
||||
for _, subkey := range entity.Subkeys {
|
||||
if decryptErr := subkey.PrivateKey.Encrypt(passphrase); decryptErr != nil {
|
||||
return fmt.Errorf("%w subkey (private key): %w", ErrOpenPGPDecryptionFailure, decryptErr)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
func TestReadKeyPairRing(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
testDataDir := "../../tests/data/openpgp-keyring"
|
||||
files, err := os.ReadDir(testDataDir)
|
||||
assert.Nil(t, err)
|
||||
|
||||
for _, entry := range files {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if ! strings.HasSuffix(strings.ToLower(entry.Name()), ".asc") {
|
||||
continue
|
||||
}
|
||||
|
||||
path := filepath.Join(testDataDir, entry.Name())
|
||||
fileData, err := ioutil.ReadFile(path)
|
||||
|
||||
declarationAttributes := strings.Builder{}
|
||||
enc := codec.NewYAMLEncoder(&declarationAttributes)
|
||||
type kr struct {
|
||||
KeyRing string `yaml:"keyring"`
|
||||
}
|
||||
assert.Nil(t, enc.Encode(&kr{ KeyRing: string(fileData) }))
|
||||
|
||||
testKeyRing := NewOpenPGPKeyRing()
|
||||
|
||||
testKeyRing.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
|
||||
switch key {
|
||||
case "passphrase":
|
||||
return "foo", nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
|
||||
}))
|
||||
|
||||
e := testKeyRing.LoadDecl(declarationAttributes.String())
|
||||
assert.Nil(t, e)
|
||||
y, err := testKeyRing.Read(ctx)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, y)
|
||||
assert.Greater(t, len(testKeyRing.entityList), 0)
|
||||
|
||||
if strings.HasSuffix(strings.ToLower(entry.Name()), "_private.asc") {
|
||||
assert.NotNil(t, testKeyRing.entityList[0].PrivateKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSignatureFailedDecodingSignature error = errors.New("Failed decoding signature")
|
||||
ErrSignatureWriterFailed error = errors.New("Failed creating signature writer")
|
||||
ErrArmoredWriterFailed error = errors.New("Failed to create armored writer")
|
||||
ErrSignatureVerificationUnknownEntity error = errors.New("Signature uses unknown entity")
|
||||
ErrSignatureMissing error = errors.New("Signature value undefined")
|
||||
)
|
||||
|
||||
const (
|
||||
@ -48,14 +51,17 @@ func init() {
|
||||
}
|
||||
|
||||
type OpenPGPSignature struct {
|
||||
*Common `json:",inline" yaml:",inline"`
|
||||
*Common `json:"-,inline" yaml:"-,inline"`
|
||||
stater machine.Stater `json:"-" yaml:"-"`
|
||||
Signature string `json:"signature,omitempty" yaml:"signature,omitempty"`
|
||||
KeyRingRef folio.ResourceReference `json:"keyringref,omitempty" yaml:"keyringref,omitempty"`
|
||||
SourceRef folio.ResourceReference `json:"soureref,omitempty" yaml:"sourceref,omitempty"`
|
||||
SignatureRef folio.ResourceReference `json:"signatureref,omitempty" yaml:"signatureref,omitempty"`
|
||||
Signed string `json:"signed,omitempty" yaml:"signed,omitempty"`
|
||||
KeyRingRef folio.Ref `json:"keyringref,omitempty" yaml:"keyringref,omitempty"`
|
||||
SourceRef folio.Ref `json:"soureref,omitempty" yaml:"sourceref,omitempty"`
|
||||
SignatureRef folio.Ref `json:"signatureref,omitempty" yaml:"signatureref,omitempty"`
|
||||
|
||||
signatureBlock *armor.Block
|
||||
message *openpgp.MessageDetails
|
||||
|
||||
entityList openpgp.EntityList
|
||||
}
|
||||
|
||||
@ -90,10 +96,32 @@ func (o *OpenPGPSignature) StateMachine() machine.Stater {
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) URI() string {
|
||||
return string(o.Common.URI())
|
||||
parsedSourceUri := o.SourceRef.Uri.Parse().URL()
|
||||
return fmt.Sprintf("%s://%s", o.Type(), parsedSourceUri.Host + parsedSourceUri.RequestURI())
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) DecryptPrivateKey(entity *openpgp.Entity) error {
|
||||
if o.config != nil {
|
||||
passphraseConfig, _ := o.config.GetValue("passphrase")
|
||||
passphrase := []byte(passphraseConfig.(string))
|
||||
if len(passphrase) > 0 {
|
||||
slog.Info("OpenPGPSignature.DecryptPrivateKey", "passphrase", passphrase, "entity", entity)
|
||||
if decryptErr := entity.PrivateKey.Decrypt(passphrase); decryptErr != nil {
|
||||
return fmt.Errorf("%w private key: %w", ErrOpenPGPDecryptionFailure, decryptErr)
|
||||
}
|
||||
for _, subkey := range entity.Subkeys {
|
||||
if decryptErr := subkey.PrivateKey.Encrypt(passphrase); decryptErr != nil {
|
||||
return fmt.Errorf("%w subkey (private key): %w", ErrOpenPGPDecryptionFailure, decryptErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) SigningEntity() (err error) {
|
||||
if len(o.entityList) < 1 {
|
||||
slog.Info("OpenPGPSignature.SigningEntity() - Loading KeyRing", "keyring", o.KeyRingRef)
|
||||
if o.KeyRingRef.IsEmpty() {
|
||||
var keyringConfig any
|
||||
if keyringConfig, err = o.config.GetValue("keyring"); err == nil {
|
||||
@ -102,29 +130,60 @@ func (o *OpenPGPSignature) SigningEntity() (err error) {
|
||||
} else {
|
||||
ringFileStream, _ := o.KeyRingRef.Lookup(o.Resources).ContentReaderStream()
|
||||
defer ringFileStream.Close()
|
||||
|
||||
o.entityList, err = openpgp.ReadArmoredKeyRing(ringFileStream)
|
||||
slog.Info("OpenPGPSignature.SigningEntity()", "entities", o.entityList[0])
|
||||
}
|
||||
for i := range o.entityList {
|
||||
if decryptErr := o.DecryptPrivateKey(o.entityList[i]); decryptErr != nil {
|
||||
err = decryptErr
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func (o *OpenPGPSignature) Sign(message io.Reader, w io.Writer) (err error) {
|
||||
var writer io.WriteCloser
|
||||
entity := o.entityList[0]
|
||||
|
||||
if writer, err = openpgp.Sign(w, entity, nil, o.Config()); err == nil {
|
||||
defer writer.Close()
|
||||
_, err = io.Copy(writer, message)
|
||||
} else {
|
||||
if err = openpgp.DetachSign(w, entity, message, o.Config()); err != nil {
|
||||
err = fmt.Errorf("%w: %w", ErrSignatureWriterFailed, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) Verify(message io.Reader) (err error) {
|
||||
if len(o.Signature) < 1 || o.signatureBlock == nil {
|
||||
return fmt.Errorf("%w - %d", ErrSignatureMissing, len(o.Signature))
|
||||
}
|
||||
|
||||
slog.Info("OpenPGPSignature.Verify()", "signature", o.Signature, "block", o.signatureBlock)
|
||||
var entity *openpgp.Entity
|
||||
entity, err = openpgp.CheckDetachedSignature(o.entityList, message, o.signatureBlock.Body, o.Config())
|
||||
if entity == nil {
|
||||
slog.Info("OpenPGPSignature.Verify() check signature failed", "signature", o.signatureBlock, "err", err)
|
||||
return fmt.Errorf("%w: %w", ErrSignatureVerificationUnknownEntity, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) Create(ctx context.Context) (err error) {
|
||||
if err = o.SigningEntity(); err == nil {
|
||||
var sourceReadStream io.ReadCloser
|
||||
|
||||
switch o.SourceRef.RefType {
|
||||
case folio.ReferenceTypeDocument:
|
||||
//sourceReadStream, err = o.SourceRef.Lookup(folio.DocumentRegistry.UriMap).ContentReaderStream()
|
||||
sourceReadStream, err = o.SourceRef.Reader()
|
||||
srcData, _ := io.ReadAll(sourceReadStream)
|
||||
sourceReadStream.Close()
|
||||
o.Signed = string(srcData)
|
||||
sourceReadStream, err = o.SourceRef.Reader()
|
||||
default:
|
||||
sourceReadStream, err = o.SourceRef.Lookup(o.Resources).ContentReaderStream()
|
||||
}
|
||||
defer sourceReadStream.Close()
|
||||
|
||||
var signatureStream, armoredWriter io.WriteCloser
|
||||
|
||||
if o.SignatureRef.IsEmpty() {
|
||||
@ -138,9 +197,9 @@ func (o *OpenPGPSignature) Create(ctx context.Context) (err error) {
|
||||
if armoredWriter, err = armor.Encode(signatureStream, openpgp.SignatureType, nil); err != nil {
|
||||
err = fmt.Errorf("%w: %w", ErrArmoredWriterFailed, err)
|
||||
}
|
||||
defer armoredWriter.Close()
|
||||
|
||||
err = o.Sign(sourceReadStream, armoredWriter)
|
||||
armoredWriter.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -196,8 +255,10 @@ func (o *OpenPGPSignature) Notify(m *machine.EventMessage) {
|
||||
_ = o.AddError(triggerErr)
|
||||
}
|
||||
} else {
|
||||
slog.Info("OpenPGPSignature.Notify()", "dest", "start_read", "err", readErr)
|
||||
_ = o.AddError(readErr)
|
||||
if o.IsResourceInconsistent() {
|
||||
slog.Info("OpenPGPSignature.Notify()", "dest", "read-failed", "machine", o.StateMachine())
|
||||
if triggerErr := o.StateMachine().Trigger("read-failed"); triggerErr == nil {
|
||||
panic(readErr)
|
||||
} else {
|
||||
@ -320,16 +381,16 @@ func (o *OpenPGPSignature) ResolveId(ctx context.Context) string {
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) GetContentSourceRef() string {
|
||||
return string(o.SignatureRef)
|
||||
return o.SignatureRef.String()
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) SetContentSourceRef(uri string) {
|
||||
o.SignatureRef = folio.ResourceReference(uri)
|
||||
o.SignatureRef.Uri = folio.URI(uri)
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) SignatureRefStat() (info fs.FileInfo, err error) {
|
||||
err = fmt.Errorf("%w: %s", ErrResourceStateAbsent, o.SignatureRef)
|
||||
if len(o.SignatureRef) > 0 {
|
||||
err = fmt.Errorf("%w: %s", ErrResourceStateAbsent, o.SignatureRef.Uri)
|
||||
if len(o.SignatureRef.Uri) > 0 {
|
||||
rs, _ := o.ContentReaderStream()
|
||||
defer rs.Close()
|
||||
return rs.Stat()
|
||||
@ -346,6 +407,16 @@ func (o *OpenPGPSignature) ReadStat() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) SourceRefStat() (info fs.FileInfo, err error) {
|
||||
err = fmt.Errorf("%w: %s", ErrResourceStateAbsent, o.SourceRef.Uri)
|
||||
if ! o.SourceRef.IsEmpty() {
|
||||
rs, _ := o.SourceRef.ContentReaderStream()
|
||||
defer rs.Close()
|
||||
return rs.Stat()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) Update(ctx context.Context) error {
|
||||
return o.Create(ctx)
|
||||
}
|
||||
@ -354,7 +425,69 @@ func (o *OpenPGPSignature) Delete(ctx context.Context) error {
|
||||
return os.Remove(o.Common.Path)
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) Read(ctx context.Context) ([]byte, error) {
|
||||
func (o *OpenPGPSignature) readSignatureRef() (err error) {
|
||||
// signatureref
|
||||
// XXX which takes precedence: the value of Signature from yaml or the value of Signature loaded from SignatureRef?
|
||||
if (! o.SignatureRef.IsEmpty()) && o.SignatureRef.Exists() {
|
||||
signatureReader, _ := o.SignatureRef.Lookup(o.Resources).ContentReaderStream()
|
||||
defer signatureReader.Close()
|
||||
SignatureData, readErr := io.ReadAll(signatureReader)
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
o.Signature = string(SignatureData)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenPGPSignature) DecodeSignatureBlock() (err error) {
|
||||
slog.Info("OpenPGPSignature.DecodeSignatureBlock()", "signature", o.Signature)
|
||||
if o.signatureBlock, err = armor.Decode(bytes.NewReader([]byte(o.Signature))); o.signatureBlock == nil || err != nil {
|
||||
err = ErrSignatureFailedDecodingSignature
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Read
|
||||
// - signature(string/ref) content
|
||||
// - keyringref -> loads signing entity
|
||||
// - sourceref to generate a signature (create)
|
||||
func (o *OpenPGPSignature) Read(ctx context.Context) (yamlData []byte, err error) {
|
||||
if len(o.Signature) < 1 {
|
||||
if err = o.readSignatureRef(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
if err = o.DecodeSignatureBlock(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
slog.Info("OpenPGPSignature.Read() - decodesignatureblock", "sourceref", o.SourceRef, "entityList", o.entityList, "signatureblock", o.signatureBlock)
|
||||
|
||||
// sourceref
|
||||
if _, sourceRefStatErr := o.SourceRefStat(); sourceRefStatErr != nil {
|
||||
return nil, sourceRefStatErr
|
||||
}
|
||||
slog.Info("OpenPGPSignature.Read() - sourceref", "sourceref", o.SourceRef, "entityList", o.entityList, "signatureblock", o.signatureBlock)
|
||||
|
||||
if err = o.SigningEntity(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
slog.Info("OpenPGPSignature.Read() - reading", "sourceref", o.SourceRef, "entityList", o.entityList, "signatureblock", o.signatureBlock)
|
||||
|
||||
|
||||
slog.Info("OpenPGPSignature.Read() - reading sourceref", "openpgp-signature", o)
|
||||
if sourceRefReader, sourceRefReaderErr := o.SourceRef.Reader(); sourceRefReaderErr != nil {
|
||||
return nil, sourceRefReaderErr
|
||||
} else {
|
||||
defer sourceRefReader.Close()
|
||||
if err = o.Verify(sourceRefReader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
o.Common.State = "present"
|
||||
|
||||
slog.Info("OpenPGPSignature.Read()", "openpgp-signature", o)
|
||||
return yaml.Marshal(o)
|
||||
}
|
||||
|
||||
|
@ -10,21 +10,208 @@ import (
|
||||
"decl/internal/data"
|
||||
"decl/internal/folio"
|
||||
"decl/internal/codec"
|
||||
"decl/internal/ext"
|
||||
"log"
|
||||
"io"
|
||||
"os"
|
||||
"bytes"
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func NewTestUserKeys() (data.ResourceMapper, folio.URI) {
|
||||
uri := "openpgp-keyring://TestUser1/TestUser1/testuser@rosskeen.house"
|
||||
var mockKeyRingPassphraseConfig MockConfigValueGetter = func(key string) (any, error) {
|
||||
switch key {
|
||||
case "passphrase":
|
||||
return "foo", nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
|
||||
}
|
||||
|
||||
func DataKeyRing(uri folio.URI) (folio.ReferenceMapper[*folio.Declaration], folio.URI) {
|
||||
var err error
|
||||
var reader io.ReadCloser
|
||||
|
||||
searchPath := folio.NewSearchPath(folio.ConfigKey("system.importpath").GetStringSlice())
|
||||
|
||||
if u := uri.Parse().(*folio.ParsedURI); u != nil {
|
||||
projectPath := filepath.Dir(filepath.Join(u.Hostname(), u.Path))
|
||||
if err := searchPath.AddPath(projectPath); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if reader, err = uri.ContentReaderStream(); err == nil {
|
||||
keyRingDecl := folio.NewDeclaration()
|
||||
if err = keyRingDecl.LoadReader(reader, codec.FormatYaml); err == nil {
|
||||
keyRing := keyRingDecl.Resource()
|
||||
testKeyRingResource := keyRing.(*OpenPGPKeyRing)
|
||||
testKeyRingResource.KeyRingRef.FindIn(searchPath)
|
||||
|
||||
keyRing.UseConfig(mockKeyRingPassphraseConfig)
|
||||
keyRing.Read(context.Background())
|
||||
|
||||
if testKeyRingResource.entityList[0].PrivateKey == nil {
|
||||
log.Fatal("Keyring does not contain a private key")
|
||||
}
|
||||
testKeyRingResource.Resources = folio.NewResourceMapper()
|
||||
testKeyRingResource.Resources.Set(folio.URI(keyRing.URI()), keyRingDecl)
|
||||
|
||||
return testKeyRingResource.Resources, folio.URI(testKeyRingResource.URI())
|
||||
}
|
||||
}
|
||||
log.Fatal(err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func NewTestUserKeys(name string, comment string, email string) (folio.ReferenceMapper[*folio.Declaration], folio.URI) {
|
||||
|
||||
uri := fmt.Sprintf("openpgp-keyring://%s/%s/%s", name, comment, email)
|
||||
keyRingDecl := folio.NewDeclaration()
|
||||
keyRingDecl.NewResource(&uri)
|
||||
|
||||
ctx := context.Background()
|
||||
declarationAttributes := fmt.Sprintf(`
|
||||
name: TestUser1
|
||||
comment: TestUser1
|
||||
email: testuser@rosskeen.house
|
||||
keyringref: file://%s/keyring.asc
|
||||
`, string(TempDir))
|
||||
name: %s
|
||||
comment: %s
|
||||
email: %s
|
||||
keyringref: file://%s/keyring_%s.asc
|
||||
`, name, comment, email, string(TempDir), name)
|
||||
|
||||
testKeyRing := keyRingDecl.Resource()
|
||||
if e := testKeyRing.LoadString(declarationAttributes, codec.FormatYaml); e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
|
||||
testKeyRing.UseConfig(mockKeyRingPassphraseConfig)
|
||||
|
||||
if err := testKeyRing.Create(ctx); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
testKeyRingResource := testKeyRing.(*OpenPGPKeyRing)
|
||||
|
||||
if testKeyRingResource.entityList[0].PrivateKey == nil {
|
||||
log.Fatal("Keyring does not contain a private key")
|
||||
}
|
||||
|
||||
testKeyRingResource.Resources = folio.NewResourceMapper()
|
||||
testKeyRingResource.Resources.Set(folio.URI(uri), keyRingDecl)
|
||||
|
||||
return testKeyRingResource.Resources, folio.URI(uri)
|
||||
}
|
||||
|
||||
func KeyRingTestUser1() (folio.ReferenceMapper[*folio.Declaration], folio.URI) {
|
||||
name := "TestUser1"
|
||||
comment := "TestUser1"
|
||||
email := "testuser@rosskeen.house"
|
||||
|
||||
if e := TempDir.CreateFile("keyring_TestUser1.asc", `
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
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
|
||||
=BAfD
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBGhI/VUBCADDVSm3mKY5JsncMMJFV0zELMrmip7dkK3vvMvVWVmMHiC4akDH
|
||||
WPxUdWNQPjE2e5HGF9Ebg0c7gu634VG470MnTzFdPV6f5zA+yJfdKrLq7gpl9QGW
|
||||
jFLIeK/l4xc+MIpOE1rD9WqYUUw2IYY8YANYq4yB36rq41VuZps/adI9Go5IhfcU
|
||||
3SVb7o7pa/gWE0FVu9ze31j2agC8FIKHgB++7bmYgbAQz5Qi1qgtG0Kn25QUacJ6
|
||||
Akm2+h4w3SQCR6HLRV2BO29x9mFBszf2KQ7DW2VNiGyUuQQ3m8v2ZidG/11ff6U6
|
||||
ad5tvr/8sYr5jOnKEJyDP9v9yQ04cU94GmsPABEBAAHNL1Rlc3RVc2VyMSAoVGVz
|
||||
dFVzZXIxKSA8dGVzdHVzZXJAcm9zc2tlZW4uaG91c2U+wsCKBBMBCAA+BQJoSP1V
|
||||
CRDfB5I2FtNl1xYhBADOnA8Aah3FXyl/m98HkjYW02XXAhsDAh4BAhkBAgsHAhUI
|
||||
AxYAAgMnBwIAAGKLB/9whwgfLkoTYI+Q0Te2uZXl7FPyW0t4tbUSoI1aiVw+ymND
|
||||
V+wuqYsjYDob2MyK/TAFkSCqZhBIRbGLRJtwzQwkF/amGuelHSSBX3LdxK/sp2UU
|
||||
+zv/NBEP1LlUNKchuxpdBPYjKHdbOLPoKqFfdCujSxpWTIeKCwvnDtuP+PlsjUgc
|
||||
afxMx+cVwe77AZ1Fi5CBD9Dr7nNsLovywrSiszyVh5eT0pbcu8Elf5oYQNT1q3ms
|
||||
aqrw95shlGQiwVgrfTvRCDeZnKafHwMUoh48otsNI9aS0HpChi+JNd+PLe/Wlsj5
|
||||
x+q0sHc+5/24zQvN0Wx9fPE4/7z8Pr+CUhAxjI7hzsBNBGhI/VUBCACfahoi2GRx
|
||||
OfOqJ4ZN8Te+4bJeOQdzTofAisfCU736q+MvEKtAuY72vzWdERWpn7XABmtkfII8
|
||||
Xyya8iHKuEGCETy0YoZ15GLBgSHhLnlN2U/4BkrUjGvUjZWquW22gfFB1q8ZdSGw
|
||||
pg8hrspY/b+nnAGdawq+hfE6YRQTmG5tkBrctxfxglBnKzezmL1a3arTHWf2SRlL
|
||||
eAWNI4sACryP/5plD6+9Z9YXTKeDlXnga6BzB80y7F2g0cPdtX20dWAGx4irq2I2
|
||||
SxlABvWMZrdm2Upr6yLHD2zTpbf1FkKFUw9j0lrL1fMq0/j9zbKJsTh6CtOlF04s
|
||||
AeoEV9SZ36GvABEBAAHCwHYEGAEIACoFAmhI/VUJEN8HkjYW02XXFiEEAM6cDwBq
|
||||
HcVfKX+b3weSNhbTZdcCGwwAAFxWB/9Af7qpYKldDz5iNYQluFoPfyHAVlOpiakf
|
||||
hvnu7hijTEgB1frByKu2m+k4m7gANBgfWldsYi1feWBVn6fspXuCc0k7opmQgol7
|
||||
ueTrsGnNB7U0l14HcKSTLZ2c/Jt7ZS6aXXjxpYPq0/5lt8+5sRkXxHI9HhAx6gK/
|
||||
RJCNITRt/V/gT4YWWbz8VHgkPIfHFsjr8brVhm4V/+JKZAG4eSNQp4QrTpdIjdfa
|
||||
1rd91qffyrWhPNTQErE2jFOXSU/OojdDjGJ7pwr0qj4FHACVL3/oqYog2NrUWf92
|
||||
QcwwWrEZNZUpJEMUB5q+tOoZ40ll0A8lsvABULZDqWveYpRQF+rQ
|
||||
=jBvZ
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
`); e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("openpgp-keyring://%s/%s/%s", name, comment, email)
|
||||
keyRingDecl := folio.NewDeclaration()
|
||||
keyRingDecl.NewResource(&uri)
|
||||
|
||||
ctx := context.Background()
|
||||
declarationAttributes := fmt.Sprintf(`
|
||||
name: %s
|
||||
comment: %s
|
||||
email: %s
|
||||
keyringref: file://%s/keyring_%s.asc
|
||||
`, name, comment, email, string(TempDir), name)
|
||||
|
||||
testKeyRing := keyRingDecl.Resource()
|
||||
if e := testKeyRing.LoadString(declarationAttributes, codec.FormatYaml); e != nil {
|
||||
@ -39,96 +226,277 @@ func NewTestUserKeys() (data.ResourceMapper, folio.URI) {
|
||||
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
|
||||
}))
|
||||
|
||||
if err := testKeyRing.Create(ctx); err != nil {
|
||||
if _, err := testKeyRing.Read(ctx); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return TestResourceMapper(func(key string) (data.Declaration, bool) {
|
||||
return keyRingDecl, true
|
||||
}), folio.URI(uri)
|
||||
|
||||
testKeyRingResource := testKeyRing.(*OpenPGPKeyRing)
|
||||
testKeyRingResource.Resources = folio.NewResourceMapper()
|
||||
|
||||
if testKeyRingResource.entityList[0].PrivateKey == nil {
|
||||
log.Fatal("Keyring does not contain a private key")
|
||||
}
|
||||
|
||||
/*
|
||||
return TestRefMapper[*folio.Declaration](func(key string) (*folio.Declaration, bool) {
|
||||
switch key {
|
||||
case uri:
|
||||
slog.Info("KEYRING", "name", name, "addr", testKeyRing.(*OpenPGPKeyRing).entityList[0].PrivateKey)
|
||||
return keyRingDecl, true
|
||||
}
|
||||
return nil, false
|
||||
}), folio.URI(uri)
|
||||
*/
|
||||
testKeyRingResource.Resources.Set(folio.URI(uri), keyRingDecl)
|
||||
|
||||
return testKeyRingResource.Resources, folio.URI(uri)
|
||||
}
|
||||
|
||||
func DataTestKeyRing(keyRingPath string) (folio.ReferenceMapper[*folio.Declaration], folio.URI) {
|
||||
|
||||
keyData, err := os.ReadFile(keyRingPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(keyData))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var name, comment, email string
|
||||
for _, entity := range keyring {
|
||||
for _, identity := range entity.Identities {
|
||||
if identity.UserId != nil {
|
||||
name = identity.UserId.Name
|
||||
comment = identity.UserId.Comment
|
||||
email = identity.UserId.Email
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uri := fmt.Sprintf("openpgp-keyring://%s/%s/%s", name, comment, email)
|
||||
keyRingDecl := folio.NewDeclaration()
|
||||
keyRingDecl.NewResource(&uri)
|
||||
|
||||
ctx := context.Background()
|
||||
declarationAttributes := fmt.Sprintf(`
|
||||
name: %s
|
||||
comment: %s
|
||||
email: %s
|
||||
keyringref: file://%s
|
||||
`, name, comment, email, keyRingPath)
|
||||
|
||||
testKeyRing := keyRingDecl.Resource()
|
||||
if e := testKeyRing.LoadString(declarationAttributes, codec.FormatYaml); e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
|
||||
testKeyRing.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
|
||||
switch key {
|
||||
case "passphrase":
|
||||
return "foo", nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
|
||||
}))
|
||||
|
||||
if _, err := testKeyRing.Read(ctx); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
testKeyRingResource := testKeyRing.(*OpenPGPKeyRing)
|
||||
testKeyRingResource.Resources = folio.NewResourceMapper()
|
||||
|
||||
if testKeyRingResource.entityList[0].PrivateKey == nil {
|
||||
log.Fatal("Keyring does not contain a private key")
|
||||
}
|
||||
|
||||
testKeyRingResource.Resources.Set(folio.URI(uri), keyRingDecl)
|
||||
|
||||
return testKeyRingResource.Resources, folio.URI(uri)
|
||||
}
|
||||
|
||||
func TestNewOpenPGPSignatureResource(t *testing.T) {
|
||||
assert.NotNil(t, NewOpenPGPSignature())
|
||||
}
|
||||
|
||||
func TestCreateSignature(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
func TestSigningEntity(t *testing.T) {
|
||||
m, keyRingUri := NewTestUserKeys("TestUser5", "TestUser5", "testuser5@rosskeen.house")
|
||||
assert.NotNil(t, m)
|
||||
assert.Equal(t, folio.URI("openpgp-keyring://TestUser5/TestUser5/testuser5@rosskeen.house"), keyRingUri)
|
||||
assert.Nil(t, TempDir.CreateFile("signing-test.txt", "test data"))
|
||||
assert.FileExists(t, fmt.Sprintf("%s/keyring_TestUser5.asc", TempDir))
|
||||
|
||||
m, keyRingUri := NewTestUserKeys()
|
||||
|
||||
assert.Nil(t, TempDir.CreateFile("test.txt", "test data"))
|
||||
testUserKeyRingResource := folio.Dereference(keyRingUri, m)
|
||||
assert.NotNil(t, testUserKeyRingResource)
|
||||
assert.NotNil(t, testUserKeyRingResource.Resource().(*OpenPGPKeyRing).entityList[0].PrivateKey)
|
||||
|
||||
declarationAttributes := fmt.Sprintf(`
|
||||
keyringref: %s
|
||||
sourceref: file://%s/test.txt
|
||||
keyringref:
|
||||
uri: %s
|
||||
sourceref:
|
||||
uri: file://%s/signing-test.txt
|
||||
`, string(keyRingUri), string(TempDir))
|
||||
|
||||
testSignature := NewOpenPGPSignature()
|
||||
e := testSignature.LoadString(declarationAttributes, codec.FormatYaml)
|
||||
assert.Nil(t, e)
|
||||
testSignature.Resources = m
|
||||
|
||||
testSignature.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
|
||||
switch key {
|
||||
case "passphrase":
|
||||
return "foo", nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
|
||||
}))
|
||||
|
||||
assert.False(t, testSignature.KeyRingRef.IsEmpty())
|
||||
slog.Info("TestSigningEntity", "keyringref", testSignature.KeyRingRef, "resourcemap", testSignature.Resources)
|
||||
|
||||
keyRingResource := testSignature.KeyRingRef.Lookup(testSignature.Resources)
|
||||
assert.NotNil(t, keyRingResource)
|
||||
|
||||
ringFileStream, streamErr := keyRingResource.ContentReaderStream()
|
||||
assert.Nil(t, streamErr)
|
||||
assert.NotNil(t, ringFileStream)
|
||||
|
||||
assert.Nil(t, testSignature.SigningEntity())
|
||||
assert.NotNil(t, testSignature.entityList)
|
||||
assert.Len(t, testSignature.entityList, 1)
|
||||
assert.NotNil(t, testSignature.entityList[0].PrivateKey)
|
||||
}
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
//ctx := context.Background()
|
||||
|
||||
m, keyRingUri := NewTestUserKeys("TestUser2", "TestUser2", "testuser2@rosskeen.house")
|
||||
assert.NotNil(t, m)
|
||||
assert.Equal(t, folio.URI("openpgp-keyring://TestUser2/TestUser2/testuser2@rosskeen.house"), keyRingUri)
|
||||
assert.Nil(t, TempDir.CreateFile("sign-test.txt", "test data"))
|
||||
|
||||
declarationAttributes := fmt.Sprintf(`
|
||||
keyringref:
|
||||
uri: %s
|
||||
sourceref:
|
||||
uri: file://%s/sign-test.txt
|
||||
`, string(keyRingUri), string(TempDir))
|
||||
|
||||
testSignature := NewOpenPGPSignature()
|
||||
testSignature.Resources = m
|
||||
e := testSignature.LoadDecl(declarationAttributes)
|
||||
|
||||
assert.Nil(t, testSignature.LoadString(declarationAttributes, codec.FormatYaml))
|
||||
|
||||
testSignature.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
|
||||
switch key {
|
||||
case "passphrase":
|
||||
return "foo", nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
|
||||
}))
|
||||
|
||||
assert.Nil(t, testSignature.SigningEntity())
|
||||
|
||||
sourceReadStream, err := testSignature.SourceRef.Lookup(testSignature.Resources).ContentReaderStream()
|
||||
var signatureStream, armoredWriter io.WriteCloser
|
||||
|
||||
assert.True(t, testSignature.SignatureRef.IsEmpty())
|
||||
|
||||
var signatureContent bytes.Buffer
|
||||
signatureStream = ext.WriteNopCloser(&signatureContent)
|
||||
defer func() { testSignature.Signature = signatureContent.String() }()
|
||||
|
||||
if armoredWriter, err = armor.Encode(signatureStream, openpgp.SignatureType, nil); err != nil {
|
||||
err = fmt.Errorf("%w: %w", ErrArmoredWriterFailed, err)
|
||||
}
|
||||
defer armoredWriter.Close()
|
||||
|
||||
slog.Info("TESTSIGN KEYRING", "name", "TestUser2", "addr", testSignature.KeyRingRef.Lookup(testSignature.Resources).(*OpenPGPKeyRing))
|
||||
assert.Len(t, testSignature.entityList, 1)
|
||||
slog.Info("Signing Entity", "entity", testSignature.entityList[0])
|
||||
assert.NotNil(t, testSignature.entityList[0].PrivateKey)
|
||||
assert.Nil(t, testSignature.Sign(sourceReadStream, armoredWriter))
|
||||
}
|
||||
|
||||
func TestCreateSignature(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
m, keyRingUri := NewTestUserKeys("TestUser3", "TestUser3", "testuser3@rosskeen.house")
|
||||
assert.NotNil(t, m)
|
||||
assert.Equal(t, folio.URI("openpgp-keyring://TestUser3/TestUser3/testuser3@rosskeen.house"), keyRingUri)
|
||||
assert.Nil(t, TempDir.CreateFile("test3.txt", "test data\n"))
|
||||
|
||||
declarationAttributes := fmt.Sprintf(`
|
||||
keyringref:
|
||||
uri: %s
|
||||
sourceref:
|
||||
uri: file://%s/test3.txt
|
||||
`, string(keyRingUri), string(TempDir))
|
||||
|
||||
testSignature := NewOpenPGPSignature()
|
||||
testSignature.Resources = m
|
||||
|
||||
testSignature.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
|
||||
switch key {
|
||||
case "passphrase":
|
||||
return "foo", nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
|
||||
}))
|
||||
|
||||
e := testSignature.LoadString(declarationAttributes, codec.FormatYaml)
|
||||
assert.Nil(t, e)
|
||||
|
||||
err := testSignature.Create(ctx)
|
||||
assert.Nil(t, err)
|
||||
/*
|
||||
|
||||
assert.Greater(t, len(testSignature.entityList), 0)
|
||||
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser1 (TestUser1) <testuser@rosskeen.house>")
|
||||
assert.Contains(t, testSignature.Signature, "-----END PGP PUBLIC KEY BLOCK-----")
|
||||
assert.Contains(t, testSignature.Signature, "-----END PGP PRIVATE KEY BLOCK-----")
|
||||
*/
|
||||
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser3 (TestUser3) <testuser3@rosskeen.house>")
|
||||
assert.NotContains(t, testSignature.Signature, "-----END PGP PUBLIC KEY BLOCK-----")
|
||||
assert.NotContains(t, testSignature.Signature, "-----END PGP PRIVATE KEY BLOCK-----")
|
||||
assert.Contains(t, testSignature.Signature, "-----END PGP SIGNATURE-----")
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
func TestReadSignature(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
declarationAttributes := `
|
||||
signature: |-
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
assert.Nil(t, TempDir.CreateFile("read-test.txt", "test data\n"))
|
||||
|
||||
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
|
||||
+aAQq8u60fWheEIG+xaV3T01zrNRUo6I7xu5kA==
|
||||
=yFbn
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
`
|
||||
m, keyRingUri := KeyRingTestUser1()
|
||||
assert.NotNil(t, m)
|
||||
assert.Equal(t, folio.URI("openpgp-keyring://TestUser1/TestUser1/testuser@rosskeen.house"), keyRingUri)
|
||||
|
||||
declarationAttributes := fmt.Sprintf(`
|
||||
keyringref:
|
||||
uri: %s
|
||||
sourceref:
|
||||
uri: file://%s/read-test.txt
|
||||
signature: |-
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQEzBAABCgAdFiEEAM6cDwBqHcVfKX+b3weSNhbTZdcFAmhQtmMACgkQ3weSNhbT
|
||||
ZddNPAf/ZSKVOUXVlIBgXMsw2RqVWYkOzyuWADeOq6P1UHlRN0xxSmOFnHukxstL
|
||||
zTa7KSpf5y3/DKRSTWldAy8WYFr0mx5hoDy5c83OYZo2oUtFsVYifol4JfZeF+ij
|
||||
6a2trW17VjtKYzwpSTt/SAaTxKj/6oVevpWykOPfg6V3CCLfnVEuBMhotU91ngbg
|
||||
wh3R3K7LrVVJ6kYI4RKUeBIx280JZMG0sZ/AuwUdWG6KFavGMbHVzHHD2PMwT53t
|
||||
xJ5kzWyS9ZWws8r/B33GM9szHplXulhETsd1S3x0/R2MoVcHeAaAa6JwD0y0Kxu6
|
||||
MuQM3twP32lK4UUqNx4oPHzU5gbirA==
|
||||
=+dVO
|
||||
-----END PGP SIGNATURE-----
|
||||
` , string(keyRingUri), string(TempDir))
|
||||
|
||||
testSignature := NewOpenPGPSignature()
|
||||
testSignature.Resources = m
|
||||
|
||||
testSignature.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
|
||||
switch key {
|
||||
case "passphrase":
|
||||
return "foo", nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
|
||||
}))
|
||||
|
||||
e := testSignature.LoadDecl(declarationAttributes)
|
||||
assert.Nil(t, e)
|
||||
y, err := testSignature.Read(ctx)
|
||||
@ -136,7 +504,175 @@ func TestReadSignature(t *testing.T) {
|
||||
assert.NotNil(t, y)
|
||||
|
||||
assert.Greater(t, len(testSignature.entityList), 0)
|
||||
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser (TestUser) <matthewrich.conf@gmail.com>")
|
||||
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser1 (TestUser1) <testuser@rosskeen.house>")
|
||||
}
|
||||
|
||||
func TestSignatureBlock(t *testing.T) {
|
||||
assert.Nil(t, TempDir.CreateFile("block-test.txt", "test data\n"))
|
||||
|
||||
m, keyRingUri := KeyRingTestUser1()
|
||||
assert.NotNil(t, m)
|
||||
assert.Equal(t, folio.URI("openpgp-keyring://TestUser1/TestUser1/testuser@rosskeen.house"), keyRingUri)
|
||||
|
||||
declarationAttributes := fmt.Sprintf(`
|
||||
keyringref:
|
||||
uri: %s
|
||||
sourceref:
|
||||
uri: file://%s/block-test.txt
|
||||
signature: |-
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQEzBAABCgAdFiEEAM6cDwBqHcVfKX+b3weSNhbTZdcFAmhQtmMACgkQ3weSNhbT
|
||||
ZddNPAf/ZSKVOUXVlIBgXMsw2RqVWYkOzyuWADeOq6P1UHlRN0xxSmOFnHukxstL
|
||||
zTa7KSpf5y3/DKRSTWldAy8WYFr0mx5hoDy5c83OYZo2oUtFsVYifol4JfZeF+ij
|
||||
6a2trW17VjtKYzwpSTt/SAaTxKj/6oVevpWykOPfg6V3CCLfnVEuBMhotU91ngbg
|
||||
wh3R3K7LrVVJ6kYI4RKUeBIx280JZMG0sZ/AuwUdWG6KFavGMbHVzHHD2PMwT53t
|
||||
xJ5kzWyS9ZWws8r/B33GM9szHplXulhETsd1S3x0/R2MoVcHeAaAa6JwD0y0Kxu6
|
||||
MuQM3twP32lK4UUqNx4oPHzU5gbirA==
|
||||
=+dVO
|
||||
-----END PGP SIGNATURE-----
|
||||
` , string(keyRingUri), string(TempDir))
|
||||
|
||||
testSignature := NewOpenPGPSignature()
|
||||
testSignature.Resources = m
|
||||
|
||||
testSignature.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
|
||||
switch key {
|
||||
case "passphrase":
|
||||
return "foo", nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
|
||||
}))
|
||||
|
||||
e := testSignature.LoadDecl(declarationAttributes)
|
||||
assert.Nil(t, e)
|
||||
err := testSignature.DecodeSignatureBlock()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestVerifySignature(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
assert.Nil(t, TempDir.CreateFile("verify-test.txt", "test data\n"))
|
||||
|
||||
m, keyRingUri := KeyRingTestUser1()
|
||||
assert.NotNil(t, m)
|
||||
assert.Equal(t, folio.URI("openpgp-keyring://TestUser1/TestUser1/testuser@rosskeen.house"), keyRingUri)
|
||||
|
||||
declarationAttributes := fmt.Sprintf(`
|
||||
keyringref:
|
||||
uri: %s
|
||||
sourceref:
|
||||
uri: file://%s/verify-test.txt
|
||||
signature: |-
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQEzBAABCgAdFiEEAM6cDwBqHcVfKX+b3weSNhbTZdcFAmhQtmMACgkQ3weSNhbT
|
||||
ZddNPAf/ZSKVOUXVlIBgXMsw2RqVWYkOzyuWADeOq6P1UHlRN0xxSmOFnHukxstL
|
||||
zTa7KSpf5y3/DKRSTWldAy8WYFr0mx5hoDy5c83OYZo2oUtFsVYifol4JfZeF+ij
|
||||
6a2trW17VjtKYzwpSTt/SAaTxKj/6oVevpWykOPfg6V3CCLfnVEuBMhotU91ngbg
|
||||
wh3R3K7LrVVJ6kYI4RKUeBIx280JZMG0sZ/AuwUdWG6KFavGMbHVzHHD2PMwT53t
|
||||
xJ5kzWyS9ZWws8r/B33GM9szHplXulhETsd1S3x0/R2MoVcHeAaAa6JwD0y0Kxu6
|
||||
MuQM3twP32lK4UUqNx4oPHzU5gbirA==
|
||||
=+dVO
|
||||
-----END PGP SIGNATURE-----
|
||||
` , string(keyRingUri), string(TempDir))
|
||||
|
||||
testSignature := NewOpenPGPSignature()
|
||||
testSignature.Resources = m
|
||||
|
||||
testSignature.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
|
||||
switch key {
|
||||
case "passphrase":
|
||||
return "foo", nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
|
||||
}))
|
||||
|
||||
e := testSignature.LoadDecl(declarationAttributes)
|
||||
assert.Nil(t, e)
|
||||
y, err := testSignature.Read(ctx)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, y)
|
||||
|
||||
assert.Greater(t, len(testSignature.entityList), 0)
|
||||
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser1 (TestUser1) <testuser@rosskeen.house>")
|
||||
assert.Equal(t, "DF07923616D365D7", fmt.Sprintf("%X", testSignature.entityList[0].PrimaryKey.KeyId))
|
||||
|
||||
sourceReadStream, sourceReadStreamErr := testSignature.SourceRef.ContentReaderStream()
|
||||
assert.Nil(t, sourceReadStreamErr)
|
||||
|
||||
assert.Nil(t, testSignature.DecodeSignatureBlock())
|
||||
assert.Nil(t, testSignature.Verify(sourceReadStream))
|
||||
sourceReadStream.Close()
|
||||
|
||||
assert.Nil(t, testSignature.DecodeSignatureBlock())
|
||||
sourceReadStream2, sourceReadStreamErr2 := testSignature.SourceRef.ContentReaderStream()
|
||||
assert.Nil(t, sourceReadStreamErr2)
|
||||
|
||||
assert.Nil(t, testSignature.Verify(sourceReadStream2))
|
||||
}
|
||||
|
||||
func TestVerifyDocumentSignature(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
assert.Nil(t, TempDir.CreateFile("verify-test.txt", "test data\n"))
|
||||
|
||||
m, keyRingUri := DataKeyRing("file://../../tests/data/openpgp-keyring/keyring_jx.yaml")
|
||||
|
||||
assert.NotNil(t, m)
|
||||
assert.Equal(t, folio.URI("openpgp-keyring://TestUser1/TestUser1/testuser@rosskeen.house"), keyRingUri)
|
||||
|
||||
// load a document
|
||||
docURI := folio.URI("file://../../examples/signed/ubuntu-dev-read.jx.yaml")
|
||||
r, readerErr := docURI.ContentReaderStream()
|
||||
assert.Nil(t, readerErr)
|
||||
assert.NotNil(t, r)
|
||||
doc := folio.DocumentRegistry.NewDocument(docURI)
|
||||
assert.Nil(t, doc.LoadReader(r, codec.FormatYaml))
|
||||
|
||||
declarationAttributes := fmt.Sprintf(`
|
||||
keyringref:
|
||||
uri: %s
|
||||
sourceref:
|
||||
uri: %s
|
||||
type: document
|
||||
` , string(keyRingUri), string(docURI))
|
||||
|
||||
testSignature := NewOpenPGPSignature()
|
||||
testSignature.Resources = m
|
||||
|
||||
testSignature.UseConfig(mockKeyRingPassphraseConfig)
|
||||
|
||||
e := testSignature.LoadDecl(declarationAttributes)
|
||||
assert.Nil(t, e)
|
||||
|
||||
createErr := testSignature.Create(ctx)
|
||||
assert.Nil(t, createErr)
|
||||
|
||||
assert.Greater(t, len(testSignature.entityList), 0)
|
||||
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser1 (TestUser1) <testuser@rosskeen.house>")
|
||||
|
||||
y, err := testSignature.Read(ctx) // XXX throws an error if no signature is present
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, y)
|
||||
|
||||
assert.Greater(t, len(testSignature.entityList), 0)
|
||||
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser1 (TestUser1) <testuser@rosskeen.house>")
|
||||
assert.Equal(t, "FAED7F3BB05EF687", fmt.Sprintf("%X", testSignature.entityList[0].PrimaryKey.KeyId))
|
||||
|
||||
sourceReadStream, sourceReadStreamErr := testSignature.SourceRef.Reader()
|
||||
assert.Nil(t, sourceReadStreamErr)
|
||||
|
||||
assert.Nil(t, testSignature.DecodeSignatureBlock())
|
||||
assert.Nil(t, testSignature.Verify(sourceReadStream))
|
||||
sourceReadStream.Close()
|
||||
|
||||
assert.Nil(t, testSignature.DecodeSignatureBlock())
|
||||
sourceReadStream2, sourceReadStreamErr2 := testSignature.SourceRef.Reader()
|
||||
assert.Nil(t, sourceReadStreamErr2)
|
||||
|
||||
assert.Nil(t, testSignature.Verify(sourceReadStream2))
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
|
@ -66,7 +66,6 @@ type Package struct {
|
||||
ReadCommand *command.Command `yaml:"-" json:"-"`
|
||||
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
||||
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
||||
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -123,10 +122,6 @@ func (p *Package) Init(u data.URIParser) error {
|
||||
return p.SetParsedURI(u)
|
||||
}
|
||||
|
||||
func (p *Package) SetResourceMapper(resources data.ResourceMapper) {
|
||||
p.Resources = resources
|
||||
}
|
||||
|
||||
func (p *Package) Clone() data.Resource {
|
||||
newp := &Package {
|
||||
Common: p.Common.Clone(),
|
||||
|
@ -88,7 +88,6 @@ type PKI struct {
|
||||
|
||||
Bits int `json:"bits" yaml:"bits"`
|
||||
EncodingType EncodingType `json:"type" yaml:"type"`
|
||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
func NewPKI() *PKI {
|
||||
@ -96,11 +95,6 @@ func NewPKI() *PKI {
|
||||
return p
|
||||
}
|
||||
|
||||
func (k *PKI) SetResourceMapper(resources data.ResourceMapper) {
|
||||
slog.Info("PKI.SetResourceMapper()", "resources", resources)
|
||||
k.Resources = resources
|
||||
}
|
||||
|
||||
func (k *PKI) Clone() data.Resource {
|
||||
return &PKI {
|
||||
Common: k.Common.Clone(),
|
||||
|
@ -12,32 +12,15 @@ _ "gopkg.in/yaml.v3"
|
||||
_ "log"
|
||||
_ "os"
|
||||
"decl/internal/transport"
|
||||
"decl/internal/ext"
|
||||
"decl/internal/codec"
|
||||
"decl/internal/data"
|
||||
"decl/internal/folio"
|
||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||
"strings"
|
||||
"testing"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type TestResourceMapper func(key string) (data.Declaration, bool)
|
||||
|
||||
func (rm TestResourceMapper) Get(key string) (data.Declaration, bool) {
|
||||
return rm(key)
|
||||
}
|
||||
|
||||
func (rm TestResourceMapper) Has(key string) (ok bool) {
|
||||
_, ok = rm(key)
|
||||
return
|
||||
}
|
||||
|
||||
func (rm TestResourceMapper) Set(key string, value data.Declaration) {
|
||||
}
|
||||
|
||||
func (rm TestResourceMapper) Delete(key string) {
|
||||
}
|
||||
|
||||
type StringContentReadWriter func() (any, error)
|
||||
|
||||
func (s StringContentReadWriter) ContentWriterStream() (*transport.Writer, error) {
|
||||
@ -62,8 +45,28 @@ func (s StringContentReadWriter) SetParsedURI(uri data.URIParser) (error) { retu
|
||||
func (s StringContentReadWriter) URI() (string) { return "" }
|
||||
func (s StringContentReadWriter) Validate() (error) { return nil }
|
||||
func (s StringContentReadWriter) ResourceType() data.TypeName { return "" }
|
||||
func (s StringContentReadWriter) Resource() data.Resource { return nil }
|
||||
func (s StringContentReadWriter) Resource() data.Resource { return stringContentReadWriterResource { StringContentReadWriter: s } }
|
||||
|
||||
type stringContentReadWriterResource struct {
|
||||
StringContentReadWriter
|
||||
}
|
||||
//URI() string { return "" }
|
||||
//SetParsedURI(URIParser) error { return nil }
|
||||
func (s stringContentReadWriterResource) Type() string { return "buffer" }
|
||||
func (s stringContentReadWriterResource) StateMachine() machine.Stater { return nil }
|
||||
func (s stringContentReadWriterResource) UseConfig(config data.ConfigurationValueGetter) { return }
|
||||
//ResolveId(context.Context) string { return }
|
||||
//LoadString(string, codec.Format) (error) { return }
|
||||
//Load([]byte, codec.Format) (error) { return }
|
||||
//LoadReader(io.ReadCloser, codec.Format) (error) { return }
|
||||
func (s stringContentReadWriterResource) Apply() error { return nil }
|
||||
func (s stringContentReadWriterResource) Create(context.Context) error { return nil }
|
||||
func (s stringContentReadWriterResource) Read(context.Context) ([]byte, error) { return nil, nil }
|
||||
func (s stringContentReadWriterResource) Update(context.Context) error { return nil }
|
||||
func (s stringContentReadWriterResource) Delete(context.Context) error { return nil }
|
||||
//Validate() error { return }
|
||||
func (s stringContentReadWriterResource) Clone() data.Resource { return nil }
|
||||
func (s stringContentReadWriterResource) SetResourceMapper(folio.ResourceMapper) { return }
|
||||
|
||||
func TestNewPKIKeysResource(t *testing.T) {
|
||||
r := NewPKI()
|
||||
@ -90,29 +93,19 @@ func TestPKIEncodeKeys(t *testing.T) {
|
||||
r.PublicKey()
|
||||
assert.NotNil(t, r.publicKey)
|
||||
|
||||
r.Resources = TestResourceMapper(func(key string) (data.Declaration, bool) {
|
||||
switch key {
|
||||
case "buffer://privatekey":
|
||||
return StringContentReadWriter(func() (any, error) {
|
||||
w := &transport.Writer{}
|
||||
w.SetStream(ext.WriteNopCloser(&privateTarget))
|
||||
return w, nil
|
||||
}), true
|
||||
case "buffer://publickey":
|
||||
return StringContentReadWriter(func() (any, error) {
|
||||
w := &transport.Writer{}
|
||||
w.SetStream(ext.WriteNopCloser(&publicTarget))
|
||||
return w, nil
|
||||
}), true
|
||||
case "buffer://certificate":
|
||||
return StringContentReadWriter(func() (any, error) {
|
||||
w := &transport.Writer{}
|
||||
w.SetStream(ext.WriteNopCloser(&certTarget))
|
||||
return w, nil
|
||||
}), true
|
||||
}
|
||||
return nil, false
|
||||
})
|
||||
r.Resources = folio.NewResourceMapper()
|
||||
|
||||
privateKeyBuffer := folio.NewDeclaration()
|
||||
privateKeyBuffer.Attributes = NewMockBufferResource("privatekey", &privateTarget)
|
||||
r.Resources.Set("buffer://privatekey", privateKeyBuffer)
|
||||
|
||||
publicKeyBuffer := folio.NewDeclaration()
|
||||
publicKeyBuffer.Attributes = NewMockBufferResource("publickey", &publicTarget)
|
||||
r.Resources.Set("buffer://publickey", publicKeyBuffer)
|
||||
|
||||
certKeyBuffer := folio.NewDeclaration()
|
||||
certKeyBuffer.Attributes = NewMockBufferResource("certificate", &certTarget)
|
||||
r.Resources.Set("buffer://certificate", certKeyBuffer)
|
||||
|
||||
r.PrivateKeyRef = folio.ResourceReference("buffer://privatekey")
|
||||
r.PublicKeyRef = folio.ResourceReference("buffer://publickey")
|
||||
|
@ -3,6 +3,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "certificate",
|
||||
"type": "object",
|
||||
"required": [],
|
||||
"properties": {
|
||||
"publickey": {
|
||||
"type": "string"
|
||||
|
9
internal/resource/schemas/constraints.schema.json
Normal file
9
internal/resource/schemas/constraints.schema.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"$id": "constraints.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "constraints",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": ["string", "number", "object", "array", "boolean", "null"]
|
||||
}
|
||||
}
|
9
internal/resource/schemas/dependencies.schema.json
Normal file
9
internal/resource/schemas/dependencies.schema.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"$id": "dependencies.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "dependencies",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "constraints.schema.json"
|
||||
}
|
||||
}
|
@ -3,9 +3,26 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "document",
|
||||
"type": "object",
|
||||
"required": [
|
||||
],
|
||||
"required": [],
|
||||
"properties": {
|
||||
"source": {
|
||||
"type": "string",
|
||||
"description": "Document URI"
|
||||
},
|
||||
"format": {
|
||||
"$ref": "codec.schema.json"
|
||||
},
|
||||
"requires": {
|
||||
"$ref": "dependencies.schema.json"
|
||||
},
|
||||
"imports": {
|
||||
"type": "array",
|
||||
"description": "List of other documents to import",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "Document URI"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"type": "array",
|
||||
"description": "Configurations list",
|
||||
@ -31,6 +48,7 @@
|
||||
{ "$ref": "iptable-declaration.schema.json" },
|
||||
{ "$ref": "network-route-declaration.schema.json" },
|
||||
{ "$ref": "openpgp-keyring-declaration.schema.json" },
|
||||
{ "$ref": "openpgp-signature-declaration.schema.json" },
|
||||
{ "$ref": "package-declaration.schema.json" },
|
||||
{ "$ref": "pki-declaration.schema.json" },
|
||||
{ "$ref": "user-declaration.schema.json" }
|
||||
|
@ -10,10 +10,22 @@
|
||||
"description": "Resource type name.",
|
||||
"enum": [ "file" ]
|
||||
},
|
||||
"transition": {
|
||||
"$ref": "storagetransition.schema.json"
|
||||
},
|
||||
"config": {
|
||||
"type": "string",
|
||||
"description": "Config name"
|
||||
},
|
||||
"onerror": {
|
||||
"type": "string",
|
||||
"description": "error handling strategy",
|
||||
"enum": [
|
||||
"stop",
|
||||
"fail",
|
||||
"skip"
|
||||
]
|
||||
},
|
||||
"attributes": {
|
||||
"$ref": "file.schema.json"
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Resource type name.",
|
||||
"enum": [ "file" ]
|
||||
"enum": [ "openpgp-keyring" ]
|
||||
},
|
||||
"config": {
|
||||
"type": "string",
|
||||
|
@ -0,0 +1,21 @@
|
||||
{
|
||||
"$id": "openpgp-signature-declaration.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "openpgp-signature-declaration",
|
||||
"type": "object",
|
||||
"required": [ "type", "attributes" ],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Resource type name.",
|
||||
"enum": [ "openpgp-signature" ]
|
||||
},
|
||||
"config": {
|
||||
"type": "string",
|
||||
"description": "Config name"
|
||||
},
|
||||
"attributes": {
|
||||
"$ref": "openpgp-signature.schema.json"
|
||||
}
|
||||
}
|
||||
}
|
23
internal/resource/schemas/openpgp-signature.schema.json
Normal file
23
internal/resource/schemas/openpgp-signature.schema.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"$id": "openpgp-signature.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "openpgp-signature",
|
||||
"type": "object",
|
||||
"required": [ ],
|
||||
"properties": {
|
||||
"signature": {
|
||||
"type": "string",
|
||||
"description": "ASCII-armored PGP signature"
|
||||
},
|
||||
"sourceref": {
|
||||
"$ref": "ref.schema.json"
|
||||
},
|
||||
"keyringref": {
|
||||
"$ref": "ref.schema.json"
|
||||
},
|
||||
"signatureref": {
|
||||
"$ref": "ref.schema.json",
|
||||
"description": "file content signature uri"
|
||||
}
|
||||
}
|
||||
}
|
17
internal/resource/schemas/ref.schema.json
Normal file
17
internal/resource/schemas/ref.schema.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"$id": "ref.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ref",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Type of object referred to",
|
||||
"enum": [ "resource", "document" ]
|
||||
},
|
||||
"uri": {
|
||||
"type": "string",
|
||||
"description": "URI of the object referred to"
|
||||
}
|
||||
}
|
||||
}
|
@ -60,7 +60,6 @@ type User struct {
|
||||
ReadCommand *command.Command `json:"-" yaml:"-"`
|
||||
UpdateCommand *command.Command `json:"-" yaml:"-"`
|
||||
DeleteCommand *command.Command `json:"-" yaml:"-"`
|
||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
func NewUser() *User {
|
||||
@ -107,10 +106,6 @@ func (u *User) NormalizePath() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) SetResourceMapper(resources data.ResourceMapper) {
|
||||
u.Resources = resources
|
||||
}
|
||||
|
||||
func (u *User) Clone() data.Resource {
|
||||
newu := &User {
|
||||
Common: u.Common,
|
||||
|
@ -20,10 +20,10 @@ type Schema struct {
|
||||
schema gojsonschema.JSONLoader
|
||||
}
|
||||
|
||||
func New(name string, fs fs.FS) *Schema {
|
||||
func New(name string, schemaFS fs.FS) *Schema {
|
||||
path := fmt.Sprintf("file://schemas/%s.schema.json", name)
|
||||
|
||||
return &Schema{schema: gojsonschema.NewReferenceLoaderFileSystem(path, http.FS(fs))}
|
||||
return &Schema{schema: gojsonschema.NewReferenceLoaderFileSystem(path, http.FS(schemaFS))}
|
||||
//return &Schema{schema: gojsonschema.NewReferenceLoader(path)}
|
||||
}
|
||||
|
||||
@ -36,10 +36,10 @@ func (s *Schema) Validate(source string) error {
|
||||
slog.Info("schema error", "source", source, "schema", s.schema, "result", result, "err", err)
|
||||
return err
|
||||
}
|
||||
slog.Info("schema.Validate()", "schema", s.schema, "result", result, "err", err)
|
||||
|
||||
if !result.Valid() {
|
||||
schemaErrors := strings.Builder{}
|
||||
schemaErrors.WriteString(fmt.Sprintf("source: %s schema: %#v ", source, s.schema))
|
||||
for _, err := range result.Errors() {
|
||||
schemaErrors.WriteString(err.String() + "\n")
|
||||
}
|
||||
@ -56,3 +56,11 @@ func (s *Schema) ValidateSchema() error {
|
||||
slog.Info("validate schema definition", "schemaloader", sl, "err", schemaErr)
|
||||
return schemaErr
|
||||
}
|
||||
|
||||
func (s *Schema) Debug(schemaFS fs.FS) {
|
||||
if files, err := fs.ReadDir(schemaFS, "schemas"); err == nil {
|
||||
for _, f := range files {
|
||||
slog.Info("Schema.Debug()", "file", f.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user