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 {
|
s["importpath"] = []string {
|
||||||
"/etc/jx/lib",
|
"/etc/jx/lib",
|
||||||
}
|
}
|
||||||
|
s["keyringpath"] = "/etc/jx/pgp/keyring.asc"
|
||||||
return &s
|
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
|
Crudder
|
||||||
Validator
|
Validator
|
||||||
Clone() Resource
|
Clone() Resource
|
||||||
SetResourceMapper(ResourceMapper)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Declaration interface {
|
type Declaration interface {
|
||||||
|
@ -149,12 +149,16 @@ func (j *JxFile) Extract(resourceSource data.Resource, filter data.ElementSelect
|
|||||||
}
|
}
|
||||||
|
|
||||||
uri := resourceSource.URI()
|
uri := resourceSource.URI()
|
||||||
if folio.DocumentRegistry.HasDocument(folio.URI(uri)) {
|
documentIndexUri := fmt.Sprintf("%s?index=%d", uri, j.index)
|
||||||
uri = fmt.Sprintf("%s?index=%d", uri, j.index)
|
|
||||||
|
doc = folio.DocumentRegistry.NewDocument(folio.URI(documentIndexUri))
|
||||||
|
if ! folio.DocumentRegistry.HasDocument(folio.URI(uri)) {
|
||||||
|
folio.DocumentRegistry.SetDocument(folio.URI(uri), doc.(*folio.Document))
|
||||||
|
doc.(*folio.Document).SetURI(uri)
|
||||||
}
|
}
|
||||||
doc = folio.DocumentRegistry.NewDocument(folio.URI(uri))
|
|
||||||
err = j.decoder.Decode(doc)
|
err = j.decoder.Decode(doc)
|
||||||
slog.Info("JxFile.Extract()", "doc", doc, "jxfile", j, "error", err)
|
slog.Info("JxFile.Extract()", "uri", uri, "doc", doc, "jxfile", j, "error", err)
|
||||||
j.index++
|
j.index++
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -73,7 +73,7 @@ func (d *Declaration) SetDocument(newDocument *Document) {
|
|||||||
d.document = newDocument
|
d.document = newDocument
|
||||||
d.SetConfig(d.document.config)
|
d.SetConfig(d.document.config)
|
||||||
d.ResourceTypes = d.document.Types()
|
d.ResourceTypes = d.document.Types()
|
||||||
d.Attributes.SetResourceMapper(d.document.uris)
|
d.Attributes.(ResourceMapSetter).SetResourceMapper(d.document.uris)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Declaration) ResolveId(ctx context.Context) string {
|
func (d *Declaration) ResolveId(ctx context.Context) string {
|
||||||
@ -209,6 +209,11 @@ func (d *Declaration) Apply(stateTransition string) (result error) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
result = stater.Trigger("read")
|
result = stater.Trigger("read")
|
||||||
|
case "restart": // XXX should only work for a process type resource
|
||||||
|
if result = stater.Trigger("restart"); result != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
result = stater.Trigger("read")
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: %s on %s", ErrUnknownStateTransition, stateTransition, d.Attributes.URI())
|
return fmt.Errorf("%w: %s on %s", ErrUnknownStateTransition, stateTransition, d.Attributes.URI())
|
||||||
case "create", "present":
|
case "create", "present":
|
||||||
@ -249,7 +254,7 @@ func (d *Declaration) SetConfig(configDoc data.Document) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if d.Config != "" { // XXX
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Declaration) GetContentReadWriter() data.ContentReadWriter {
|
||||||
|
return d.Resource().(data.ContentReadWriter)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
func (l *LuaWorker) Receive(m message.Envelope) {
|
func (l *LuaWorker) Receive(m message.Envelope) {
|
||||||
s := m.Sender()
|
s := m.Sender()
|
||||||
|
@ -37,7 +37,7 @@ type Document struct {
|
|||||||
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
Requires Dependencies `json:"requires,omitempty" yaml:"requires,omitempty"`
|
||||||
Imports []URI `json:"imports,omitempty" yaml:"imports,omitempty"`
|
Imports []URI `json:"imports,omitempty" yaml:"imports,omitempty"`
|
||||||
Errors []string `json:"error,omitempty" yaml:"error,omitempty"`
|
Errors []string `json:"error,omitempty" yaml:"error,omitempty"`
|
||||||
uris mapper.Store[string, data.Declaration]
|
uris mapper.Store[URI, *Declaration]
|
||||||
ResourceDeclarations []*Declaration `json:"resources,omitempty" yaml:"resources,omitempty"`
|
ResourceDeclarations []*Declaration `json:"resources,omitempty" yaml:"resources,omitempty"`
|
||||||
configNames mapper.Store[string, data.Block] `json:"-" yaml:"-"`
|
configNames mapper.Store[string, data.Block] `json:"-" yaml:"-"`
|
||||||
Configurations []*Block `json:"configurations,omitempty" yaml:"configurations,omitempty"`
|
Configurations []*Block `json:"configurations,omitempty" yaml:"configurations,omitempty"`
|
||||||
@ -57,7 +57,7 @@ func NewDocument(r *Registry) *Document {
|
|||||||
return &Document{
|
return &Document{
|
||||||
Registry: r,
|
Registry: r,
|
||||||
Format: codec.FormatYaml,
|
Format: codec.FormatYaml,
|
||||||
uris: mapper.New[string, data.Declaration](),
|
uris: mapper.New[URI, *Declaration](),
|
||||||
configNames: mapper.New[string, data.Block](),
|
configNames: mapper.New[string, data.Block](),
|
||||||
importPaths: NewSearchPath(configImportPath.GetStringSlice()),
|
importPaths: NewSearchPath(configImportPath.GetStringSlice()),
|
||||||
}
|
}
|
||||||
@ -111,24 +111,24 @@ func (d *Document) Filter(filter data.DeclarationSelector) []data.Declaration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Has(key string) bool {
|
func (d *Document) Has(key string) bool {
|
||||||
return d.uris.Has(key)
|
return d.uris.Has(URI(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Get(key string) (any, bool) {
|
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) {
|
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) {
|
func (d *Document) Delete(key string) {
|
||||||
d.uris.Delete(key)
|
d.uris.Delete(URI(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) GetResource(uri string) *Declaration {
|
func (d *Document) GetResource(uri string) *Declaration {
|
||||||
if decl, ok := d.uris[uri]; ok {
|
if decl, ok := d.uris[URI(uri)]; ok {
|
||||||
return decl.(*Declaration)
|
return decl
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -236,17 +236,17 @@ func (d *Document) GetSchemaFiles() (schemaFs fs.FS) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
schemaFs, _ = d.Registry.Schemas.Get(d.Registry.DefaultSchema)
|
schemaFs, _ = d.Registry.Schemas.Get(d.Registry.DefaultSchema)
|
||||||
slog.Info("Document.GetSchemaFiles()", "schemaFs", schemaFs)
|
slog.Info("Document.GetSchemaFiles() default schema", "schema", d.Registry.DefaultSchema, "schemaFs", schemaFs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) Validate() error {
|
func (d *Document) Validate() error {
|
||||||
jsonDocument, jsonErr := d.JSON()
|
jsonDocument, jsonErr := d.JSON()
|
||||||
slog.Info("Document.Validate() json", "err", jsonErr)
|
slog.Info("Document.Validate() convert to json", "err", jsonErr)
|
||||||
if jsonErr == nil {
|
if jsonErr == nil {
|
||||||
s := schema.New("document", d.GetSchemaFiles())
|
s := schema.New("document", d.GetSchemaFiles())
|
||||||
err := s.Validate(string(jsonDocument))
|
err := s.Validate(string(jsonDocument))
|
||||||
slog.Info("Document.Validate()", "error", err)
|
slog.Info("Document.Validate() validate schema", "error", err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -356,12 +356,12 @@ func (d *Document) MapConfigurationURI(uri string, block data.Block) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (d *Document) MapResourceURI(uri string, declaration data.Declaration) {
|
func (d *Document) MapResourceURI(uri string, declaration *Declaration) {
|
||||||
d.uris[uri] = declaration
|
d.uris[URI(uri)] = declaration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) UnMapResourceURI(uri string) {
|
func (d *Document) UnMapResourceURI(uri string) {
|
||||||
d.uris.Delete(uri)
|
d.uris.Delete(URI(uri))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) AddDeclaration(declaration data.Declaration) {
|
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.ResourceDeclarations = append(d.ResourceDeclarations, decl)
|
||||||
|
|
||||||
d.MapResourceURI(uri, declaration)
|
d.MapResourceURI(uri, decl)
|
||||||
decl.SetDocument(d)
|
decl.SetDocument(d)
|
||||||
d.Registry.DeclarationMap[decl] = 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 {
|
func (d *Document) UnmarshalValue(value *DocumentType) error {
|
||||||
d.Requires = value.Requires
|
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) {
|
func (d *Document) UnmarshalYAML(value *yaml.Node) (err error) {
|
||||||
type decodeDocument Document
|
type decodeDocument Document
|
||||||
|
/*
|
||||||
t := &DocumentType{}
|
t := &DocumentType{}
|
||||||
if unmarshalDocumentErr := value.Decode(t); unmarshalDocumentErr != nil {
|
if unmarshalDocumentErr := value.Decode(t); unmarshalDocumentErr != nil {
|
||||||
return unmarshalDocumentErr
|
return unmarshalDocumentErr
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
if unmarshalResourcesErr := value.Decode((*decodeDocument)(d)); unmarshalResourcesErr != nil {
|
if unmarshalResourcesErr := value.Decode((*decodeDocument)(d)); unmarshalResourcesErr != nil {
|
||||||
return unmarshalResourcesErr
|
return unmarshalResourcesErr
|
||||||
}
|
}
|
||||||
err = d.loadImports()
|
err = d.UnmarshalLoadDependencies()
|
||||||
d.assignConfigurationsDocument()
|
|
||||||
d.assignResourcesDocument()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -622,12 +638,39 @@ func (d *Document) UnmarshalJSON(data []byte) (err error) {
|
|||||||
if unmarshalDocumentErr := json.Unmarshal(data, t); unmarshalDocumentErr != nil {
|
if unmarshalDocumentErr := json.Unmarshal(data, t); unmarshalDocumentErr != nil {
|
||||||
return unmarshalDocumentErr
|
return unmarshalDocumentErr
|
||||||
}
|
}
|
||||||
err = d.loadImports()
|
err = d.UnmarshalLoadDependencies()
|
||||||
d.assignConfigurationsDocument()
|
|
||||||
d.assignResourcesDocument()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) AddError(e error) {
|
func (d *Document) AddError(e error) {
|
||||||
d.Errors = append(d.Errors, e.Error())
|
d.Errors = append(d.Errors, e.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Document) GetContentReadWriter() data.ContentReadWriter {
|
||||||
|
return d.URI
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Document) GetContent(w io.Writer) (contentReader io.ReadCloser, err error) {
|
||||||
|
slog.Info("Document.GetContent()")
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
err = codec.FormatYaml.Serialize(d, &buf)
|
||||||
|
|
||||||
|
contentReader = io.NopCloser(strings.NewReader(buf.String()))
|
||||||
|
|
||||||
|
if w != nil {
|
||||||
|
copyBuffer := make([]byte, 32 * 1024)
|
||||||
|
_, writeErr := io.CopyBuffer(w, contentReader, copyBuffer)
|
||||||
|
if writeErr != nil {
|
||||||
|
return nil, fmt.Errorf("File.GetContent(): CopyBuffer failed %v %v: %w", w, contentReader, writeErr)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Document) SetContent(r io.Reader) error {
|
||||||
|
return d.LoadReader(io.NopCloser(r), codec.FormatYaml)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ _ "gopkg.in/yaml.v3"
|
|||||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
|
"decl/internal/transport"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -89,9 +90,10 @@ func NewMockResource(typename string, stater machine.Stater) (m *MockResource) {
|
|||||||
InjectUpdate: func(context.Context) error { return nil },
|
InjectUpdate: func(context.Context) error { return nil },
|
||||||
InjectDelete: func(context.Context) error { return nil },
|
InjectDelete: func(context.Context) error { return nil },
|
||||||
InjectUseConfig: func(data.ConfigurationValueGetter) {},
|
InjectUseConfig: func(data.ConfigurationValueGetter) {},
|
||||||
InjectSetResourceMapper: func(data.ResourceMapper) {},
|
InjectSetResourceMapper: func(ResourceMapper) {},
|
||||||
InjectURI: func() string { return fmt.Sprintf("%s://bar", typename) },
|
InjectURI: func() string { return fmt.Sprintf("%s://bar", typename) },
|
||||||
InjectNotify: func(*machine.EventMessage) {},
|
InjectNotify: func(*machine.EventMessage) {},
|
||||||
|
InjectContentReaderStream: func() (*transport.Reader, error) { return nil, nil },
|
||||||
}
|
}
|
||||||
m.InjectInit = func(u data.URIParser) error {
|
m.InjectInit = func(u data.URIParser) error {
|
||||||
if u != nil {
|
if u != nil {
|
||||||
|
@ -11,6 +11,7 @@ _ "fmt"
|
|||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"io"
|
"io"
|
||||||
|
"decl/internal/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MockResource struct {
|
type MockResource struct {
|
||||||
@ -34,9 +35,11 @@ type MockResource struct {
|
|||||||
InjectUpdate func(context.Context) error `json:"-" yaml:"-"`
|
InjectUpdate func(context.Context) error `json:"-" yaml:"-"`
|
||||||
InjectDelete func(context.Context) error `json:"-" yaml:"-"`
|
InjectDelete func(context.Context) error `json:"-" yaml:"-"`
|
||||||
InjectStateMachine func() machine.Stater `json:"-" yaml:"-"`
|
InjectStateMachine func() machine.Stater `json:"-" yaml:"-"`
|
||||||
InjectSetResourceMapper func(data.ResourceMapper) `json:"-" yaml:"-"`
|
InjectSetResourceMapper func(ResourceMapper) `json:"-" yaml:"-"`
|
||||||
InjectUseConfig func(data.ConfigurationValueGetter) `json:"-" yaml:"-"`
|
InjectUseConfig func(data.ConfigurationValueGetter) `json:"-" yaml:"-"`
|
||||||
InjectNotify func(*machine.EventMessage) `json:"-" yaml:"-"`
|
InjectNotify func(*machine.EventMessage) `json:"-" yaml:"-"`
|
||||||
|
InjectContentReaderStream func() (*transport.Reader, error) `json:"-" yaml:"-"`
|
||||||
|
InjectContentWriterStream func() (*transport.Writer, error) `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) Clone() data.Resource {
|
func (m *MockResource) Clone() data.Resource {
|
||||||
@ -67,7 +70,7 @@ func (m *MockResource) ResolveId(ctx context.Context) string {
|
|||||||
return m.InjectResolveId(ctx)
|
return m.InjectResolveId(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) SetResourceMapper(rm data.ResourceMapper) {
|
func (m *MockResource) SetResourceMapper(rm ResourceMapper) {
|
||||||
m.InjectSetResourceMapper(rm)
|
m.InjectSetResourceMapper(rm)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +142,14 @@ func (m *MockResource) Notify(em *machine.EventMessage) {
|
|||||||
m.InjectNotify(em)
|
m.InjectNotify(em)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockResource) ContentReaderStream() (*transport.Reader, error) {
|
||||||
|
return m.InjectContentReaderStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockResource) ContentWriterStream() (*transport.Writer, error) {
|
||||||
|
return m.InjectContentWriterStream()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MockResource) UnmarshalJSON(data []byte) error {
|
func (m *MockResource) UnmarshalJSON(data []byte) error {
|
||||||
if err := json.Unmarshal(data, m); err != nil {
|
if err := json.Unmarshal(data, m); err != nil {
|
||||||
return err
|
return err
|
||||||
|
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) {
|
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) {
|
func (r *Registry) SetDocument(key URI, value *Document) {
|
||||||
@ -72,7 +73,7 @@ func (r *Registry) NewDocument(uri URI) (doc *Document) {
|
|||||||
doc.SetURI(string(uri))
|
doc.SetURI(string(uri))
|
||||||
r.Documents = append(r.Documents, doc)
|
r.Documents = append(r.Documents, doc)
|
||||||
if uri != "" {
|
if uri != "" {
|
||||||
r.UriMap[uri] = doc
|
r.UriMap.Set(uri, doc)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ package folio
|
|||||||
import (
|
import (
|
||||||
"decl/internal/transport"
|
"decl/internal/transport"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
|
"decl/internal/mapper"
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -31,25 +32,26 @@ type ContentReadWriter interface {
|
|||||||
type ResourceReference URI
|
type ResourceReference URI
|
||||||
|
|
||||||
// Return a Content ReadWriter for the resource referred to.
|
// Return a Content ReadWriter for the resource referred to.
|
||||||
func (r ResourceReference) Lookup(look data.ResourceMapper) ContentReadWriter {
|
func (r ResourceReference) Lookup(look mapper.Map[URI, *Declaration]) ContentReadWriter {
|
||||||
if string(r) == "" {
|
if string(r) == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
slog.Info("ResourceReference.Lookup()", "resourcereference", r, "resourcemapper", look)
|
slog.Info("ResourceReference.Lookup()", "resourcereference", r, "resourcemapper", look)
|
||||||
if look != nil {
|
if look != nil {
|
||||||
if v,ok := look.Get(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 v.Resource().(ContentReadWriter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return r
|
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)
|
slog.Info("ResourceReference.Dereference()", "resourcereference", r, "resourcemapper", look)
|
||||||
if look != nil {
|
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)
|
slog.Info("ResourceReference.Dereference()", "resourcereference", r, "result", v)
|
||||||
return v.(*Declaration).Attributes
|
return v.Attributes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -74,3 +76,7 @@ func (r ResourceReference) ContentWriterStream() (*transport.Writer, error) {
|
|||||||
func (r ResourceReference) IsEmpty() bool {
|
func (r ResourceReference) IsEmpty() bool {
|
||||||
return URI(r).IsEmpty()
|
return URI(r).IsEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ResourceReference) FindIn(s *SearchPath) {
|
||||||
|
(*URI)(r).FindIn(s)
|
||||||
|
}
|
||||||
|
@ -6,19 +6,18 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
"fmt"
|
"fmt"
|
||||||
"decl/internal/data"
|
|
||||||
"decl/internal/mapper"
|
"decl/internal/mapper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResourceReference(t *testing.T) {
|
func TestResourceReference(t *testing.T) {
|
||||||
f := NewFooResource()
|
f := NewFooResource()
|
||||||
resourceMapper := mapper.New[string, data.Declaration]()
|
resourceMapper := mapper.New[URI, *Declaration]()
|
||||||
f.Name = string(TempDir)
|
f.Name = string(TempDir)
|
||||||
f.Size = 10
|
f.Size = 10
|
||||||
d := NewDeclaration()
|
d := NewDeclaration()
|
||||||
d.Type = "foo"
|
d.Type = "foo"
|
||||||
d.Attributes = f
|
d.Attributes = f
|
||||||
resourceMapper[d.URI()] = d
|
resourceMapper[URI(d.URI())] = d
|
||||||
|
|
||||||
var foo ResourceReference = ResourceReference(fmt.Sprintf("foo://%s", string(TempDir)))
|
var foo ResourceReference = ResourceReference(fmt.Sprintf("foo://%s", string(TempDir)))
|
||||||
u := foo.Parse()
|
u := foo.Parse()
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "foo name",
|
"description": "foo name",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
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"
|
"path/filepath"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/folio"
|
"decl/internal/folio"
|
||||||
|
_ "decl/internal/mapper"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"errors"
|
"errors"
|
||||||
)
|
)
|
||||||
@ -31,7 +32,7 @@ type Common struct {
|
|||||||
|
|
||||||
State string `json:"state,omitempty" yaml:"state,omitempty"`
|
State string `json:"state,omitempty" yaml:"state,omitempty"`
|
||||||
config data.ConfigurationValueGetter
|
config data.ConfigurationValueGetter
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
Resources folio.ResourceMapper `json:"-" yaml:"-"`
|
||||||
Errors []error `json:"-" yaml:"-"`
|
Errors []error `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ func (c *Common) ContentType() string {
|
|||||||
return c.exttype
|
return c.exttype
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) SetResourceMapper(resources data.ResourceMapper) {
|
func (c *Common) SetResourceMapper(resources folio.ResourceMapper) {
|
||||||
c.Resources = resources
|
c.Resources = resources
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +159,7 @@ func (c *Common) IsResourceInconsistent() (result bool) {
|
|||||||
|
|
||||||
func (c *Common) AddError(err error) (error) {
|
func (c *Common) AddError(err error) (error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
slog.Info("Common.AddError()", "errors", c.Errors, "err", err)
|
||||||
c.Errors = append(c.Errors, err)
|
c.Errors = append(c.Errors, err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
@ -32,15 +32,22 @@ _ "os/exec"
|
|||||||
"decl/internal/containerlog"
|
"decl/internal/containerlog"
|
||||||
"bytes"
|
"bytes"
|
||||||
_ "encoding/base64"
|
_ "encoding/base64"
|
||||||
|
"time"
|
||||||
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ContainerTypeName TypeName = "container"
|
ContainerTypeName TypeName = "container"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrContainerWaitTimeOut = errors.New("Container wait timed out waiting for state")
|
||||||
|
)
|
||||||
|
|
||||||
type ContainerClient interface {
|
type ContainerClient interface {
|
||||||
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
|
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
|
||||||
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error
|
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error
|
||||||
|
ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error
|
||||||
ContainerList(context.Context, container.ListOptions) ([]types.Container, error)
|
ContainerList(context.Context, container.ListOptions) ([]types.Container, error)
|
||||||
ContainerInspect(context.Context, string) (types.ContainerJSON, error)
|
ContainerInspect(context.Context, string) (types.ContainerJSON, error)
|
||||||
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
ContainerRemove(context.Context, string, container.RemoveOptions) error
|
||||||
@ -91,7 +98,6 @@ type Container struct {
|
|||||||
Stderr string `json:"stderr,omitempty" yaml:"stderr,omitempty"`
|
Stderr string `json:"stderr,omitempty" yaml:"stderr,omitempty"`
|
||||||
|
|
||||||
apiClient ContainerClient
|
apiClient ContainerClient
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -137,10 +143,6 @@ func (c *Container) SetParsedURI(u data.URIParser) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) SetResourceMapper(resources data.ResourceMapper) {
|
|
||||||
c.Resources = resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Clone() data.Resource {
|
func (c *Container) Clone() data.Resource {
|
||||||
return &Container {
|
return &Container {
|
||||||
Id: c.Id,
|
Id: c.Id,
|
||||||
@ -233,7 +235,6 @@ func (c *Container) Notify(m *machine.EventMessage) {
|
|||||||
panic(createErr)
|
panic(createErr)
|
||||||
}
|
}
|
||||||
case "start_delete":
|
case "start_delete":
|
||||||
slog.Info("Container.Notify()", "event", "start_delete")
|
|
||||||
if deleteErr := c.Delete(ctx); deleteErr == nil {
|
if deleteErr := c.Delete(ctx); deleteErr == nil {
|
||||||
if triggerErr := c.StateMachine().Trigger("deleted"); triggerErr == nil {
|
if triggerErr := c.StateMachine().Trigger("deleted"); triggerErr == nil {
|
||||||
return
|
return
|
||||||
@ -245,6 +246,26 @@ func (c *Container) Notify(m *machine.EventMessage) {
|
|||||||
c.Common.State = "present"
|
c.Common.State = "present"
|
||||||
panic(deleteErr)
|
panic(deleteErr)
|
||||||
}
|
}
|
||||||
|
case "restarting":
|
||||||
|
if restartErr := c.Restart(ctx); restartErr == nil {
|
||||||
|
if triggerErr := c.StateMachine().Trigger("restarted"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
_ = c.AddError(triggerErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ = c.AddError(restartErr)
|
||||||
|
if c.IsResourceInconsistent() {
|
||||||
|
if triggerErr := c.StateMachine().Trigger("restart-failed"); triggerErr == nil {
|
||||||
|
panic(restartErr)
|
||||||
|
} else {
|
||||||
|
_ = c.AddError(triggerErr)
|
||||||
|
panic(fmt.Errorf("%w - %w", restartErr, triggerErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = c.StateMachine().Trigger("notexists")
|
||||||
|
panic(restartErr)
|
||||||
|
}
|
||||||
case "present", "created", "read":
|
case "present", "created", "read":
|
||||||
c.Common.State = "present"
|
c.Common.State = "present"
|
||||||
case "running":
|
case "running":
|
||||||
@ -475,10 +496,10 @@ func (c *Container) Read(ctx context.Context) ([]byte, error) {
|
|||||||
|
|
||||||
func (c *Container) Inspect(ctx context.Context, containerID string) error {
|
func (c *Container) Inspect(ctx context.Context, containerID string) error {
|
||||||
containerJSON, err := c.apiClient.ContainerInspect(ctx, containerID)
|
containerJSON, err := c.apiClient.ContainerInspect(ctx, containerID)
|
||||||
if client.IsErrNotFound(err) {
|
if client.IsErrNotFound(err) || containerJSON.State == nil {
|
||||||
c.Common.State = "absent"
|
c.Common.State = "absent"
|
||||||
} else {
|
} else {
|
||||||
c.Common.State = "present"
|
c.Common.State = containerJSON.State.Status
|
||||||
c.Id = containerJSON.ID
|
c.Id = containerJSON.ID
|
||||||
if c.Name == "" {
|
if c.Name == "" {
|
||||||
if containerJSON.Name[0] == '/' {
|
if containerJSON.Name[0] == '/' {
|
||||||
@ -583,3 +604,64 @@ func (c *Container) ResolveId(ctx context.Context) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WaitCondition func(state *types.ContainerState) bool
|
||||||
|
func (c *Container) wait(ctx context.Context, untilstate WaitCondition) error {
|
||||||
|
statusCh := make(chan bool)
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, 60 * time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if containerJSON, err := c.apiClient.ContainerInspect(ctx, c.Id); err == nil {
|
||||||
|
statusCh <- untilstate(containerJSON.State)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-timeoutCtx.Done():
|
||||||
|
return ErrContainerWaitTimeOut
|
||||||
|
case <-statusCh:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) Restart(ctx context.Context) (err error) {
|
||||||
|
if err = c.apiClient.ContainerRestart(ctx, c.Id, container.StopOptions{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = c.wait(ctx, func(state *types.ContainerState) bool {
|
||||||
|
return state.Running
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
waitTimeout := 60 * time.Second
|
||||||
|
interval := 2 * time.Second
|
||||||
|
deadline := time.Now().Add(waitTimeout)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if time.Now().After(deadline) {
|
||||||
|
panic("")
|
||||||
|
}
|
||||||
|
if state.Running {
|
||||||
|
if state.Health != nil {
|
||||||
|
fmt.Println("Health status:", state.Health.Status)
|
||||||
|
if state.Health.Status == "healthy" {
|
||||||
|
fmt.Println("Container is healthy and running.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("Container is running (no health check defined).")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(interval)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -93,7 +93,6 @@ type ContainerImage struct {
|
|||||||
outputWriter strings.Builder `json:"-" yaml:"-"`
|
outputWriter strings.Builder `json:"-" yaml:"-"`
|
||||||
|
|
||||||
apiClient ContainerImageClient
|
apiClient ContainerImageClient
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
|
||||||
contextDocument data.Document `json:"-" yaml:"-"`
|
contextDocument data.Document `json:"-" yaml:"-"`
|
||||||
ConverterTypes data.TypesRegistry[data.Converter] `json:"-" yaml:"-"`
|
ConverterTypes data.TypesRegistry[data.Converter] `json:"-" yaml:"-"`
|
||||||
|
|
||||||
@ -193,10 +192,6 @@ func (c *ContainerImage) NormalizePath() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ContainerImage) SetResourceMapper(resources data.ResourceMapper) {
|
|
||||||
c.Resources = resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerImage) Clone() data.Resource {
|
func (c *ContainerImage) Clone() data.Resource {
|
||||||
return &ContainerImage {
|
return &ContainerImage {
|
||||||
Common: c.Common,
|
Common: c.Common,
|
||||||
|
@ -214,8 +214,8 @@ attributes:
|
|||||||
_, readErr := contextFile.Resource().Read(context.Background())
|
_, readErr := contextFile.Resource().Read(context.Background())
|
||||||
assert.Nil(t, readErr)
|
assert.Nil(t, readErr)
|
||||||
|
|
||||||
c.Resources = data.NewResourceMapper()
|
c.Resources = folio.NewResourceMapper()
|
||||||
c.Resources.Set(contextDirUri, contextFile)
|
c.Resources.Set(folio.URI(contextDirUri), contextFile)
|
||||||
|
|
||||||
d, contextErr := c.ContextDocument()
|
d, contextErr := c.ContextDocument()
|
||||||
assert.Nil(t, contextErr)
|
assert.Nil(t, contextErr)
|
||||||
|
@ -49,7 +49,6 @@ type ContainerNetwork struct {
|
|||||||
Created time.Time `json:"created" yaml:"created"`
|
Created time.Time `json:"created" yaml:"created"`
|
||||||
|
|
||||||
apiClient ContainerNetworkClient
|
apiClient ContainerNetworkClient
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -92,10 +91,6 @@ func (n *ContainerNetwork) NormalizePath() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *ContainerNetwork) SetResourceMapper(resources data.ResourceMapper) {
|
|
||||||
n.Resources = resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ContainerNetwork) Clone() data.Resource {
|
func (n *ContainerNetwork) Clone() data.Resource {
|
||||||
return &ContainerNetwork {
|
return &ContainerNetwork {
|
||||||
Common: n.Common.Clone(),
|
Common: n.Common.Clone(),
|
||||||
|
@ -5,6 +5,7 @@ package resource
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"decl/tests/mocks"
|
"decl/tests/mocks"
|
||||||
|
"decl/internal/codec"
|
||||||
_ "encoding/json"
|
_ "encoding/json"
|
||||||
_ "fmt"
|
_ "fmt"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
@ -20,6 +21,7 @@ _ "os"
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewContainerResource(t *testing.T) {
|
func TestNewContainerResource(t *testing.T) {
|
||||||
@ -47,6 +49,9 @@ func TestReadContainer(t *testing.T) {
|
|||||||
ID: "123456789abc",
|
ID: "123456789abc",
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Image: "alpine",
|
Image: "alpine",
|
||||||
|
State: &types.ContainerState{
|
||||||
|
Status: "running",
|
||||||
|
},
|
||||||
}}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
||||||
@ -62,18 +67,19 @@ func TestReadContainer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c := NewContainer(m)
|
c := NewContainer(m)
|
||||||
assert.NotEqual(t, nil, c)
|
assert.NotNil(t, c)
|
||||||
|
|
||||||
e := c.LoadDecl(decl)
|
e := c.LoadDecl(decl)
|
||||||
assert.Equal(t, nil, e)
|
assert.Nil(t, e)
|
||||||
assert.Equal(t, "testcontainer", c.Name)
|
assert.Equal(t, "testcontainer", c.Name)
|
||||||
|
|
||||||
resourceYaml, readContainerErr := c.Read(ctx)
|
resourceYaml, readContainerErr := c.Read(ctx)
|
||||||
assert.Equal(t, nil, readContainerErr)
|
assert.Nil(t, readContainerErr)
|
||||||
assert.Greater(t, len(resourceYaml), 0)
|
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{
|
m := &mocks.MockContainerClient{
|
||||||
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
|
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
|
||||||
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
|
||||||
@ -148,3 +154,119 @@ func TestContainerLogOutput(t *testing.T) {
|
|||||||
assert.Equal(t, "done.", c.Stdout)
|
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"`
|
volume.Volume `json:",inline" yaml:",inline"`
|
||||||
|
|
||||||
apiClient ContainerVolumeClient
|
apiClient ContainerVolumeClient
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -85,10 +84,6 @@ func (v *ContainerVolume) NormalizePath() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *ContainerVolume) SetResourceMapper(resources data.ResourceMapper) {
|
|
||||||
v.Resources = resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *ContainerVolume) Clone() data.Resource {
|
func (v *ContainerVolume) Clone() data.Resource {
|
||||||
return &ContainerVolume {
|
return &ContainerVolume {
|
||||||
Common: v.Common.Clone(),
|
Common: v.Common.Clone(),
|
||||||
|
@ -34,8 +34,6 @@ type Exec struct {
|
|||||||
ReadTemplate *command.Command `yaml:"read,omitempty" json:"read,omitempty"`
|
ReadTemplate *command.Command `yaml:"read,omitempty" json:"read,omitempty"`
|
||||||
UpdateTemplate *command.Command `yaml:"update,omitempty" json:"update,omitempty"`
|
UpdateTemplate *command.Command `yaml:"update,omitempty" json:"update,omitempty"`
|
||||||
DeleteTemplate *command.Command `yaml:"delete,omitempty" json:"delete,omitempty"`
|
DeleteTemplate *command.Command `yaml:"delete,omitempty" json:"delete,omitempty"`
|
||||||
|
|
||||||
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -63,10 +61,6 @@ func NewExec() *Exec {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Exec) SetResourceMapper(resources data.ResourceMapper) {
|
|
||||||
x.Resources = resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Exec) Clone() data.Resource {
|
func (x *Exec) Clone() data.Resource {
|
||||||
return &Exec {
|
return &Exec {
|
||||||
Common: x.Common.Clone(),
|
Common: x.Common.Clone(),
|
||||||
|
@ -35,7 +35,16 @@ func TestExecApplyResourceTransformation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadExec(t *testing.T) {
|
func TestReadExec(t *testing.T) {
|
||||||
|
x := NewExec()
|
||||||
|
decl := `
|
||||||
|
read:
|
||||||
|
path: ls
|
||||||
|
args:
|
||||||
|
- -al
|
||||||
|
`
|
||||||
|
assert.Nil(t, x.LoadDecl(decl))
|
||||||
|
assert.Equal(t, "ls", x.ReadTemplate.Path)
|
||||||
|
assert.Equal(t, command.CommandArg("-al"), x.ReadTemplate.Args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadExecError(t *testing.T) {
|
func TestReadExecError(t *testing.T) {
|
||||||
|
@ -48,7 +48,6 @@ type Group struct {
|
|||||||
ReadCommand *command.Command `json:"-" yaml:"-"`
|
ReadCommand *command.Command `json:"-" yaml:"-"`
|
||||||
UpdateCommand *command.Command `json:"-" yaml:"-"`
|
UpdateCommand *command.Command `json:"-" yaml:"-"`
|
||||||
DeleteCommand *command.Command `json:"-" yaml:"-"`
|
DeleteCommand *command.Command `json:"-" yaml:"-"`
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
|
||||||
groupStatus *user.Group `json:"-" yaml:"-"`
|
groupStatus *user.Group `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,10 +101,6 @@ func (g *Group) NormalizePath() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) SetResourceMapper(resources data.ResourceMapper) {
|
|
||||||
g.Resources = resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Group) Clone() data.Resource {
|
func (g *Group) Clone() data.Resource {
|
||||||
newg := &Group {
|
newg := &Group {
|
||||||
Common: g.Common,
|
Common: g.Common,
|
||||||
|
@ -91,7 +91,6 @@ type HTTP struct {
|
|||||||
LastModified time.Time `json:"lastmodified,omitempty" yaml:"lastmodified,omitempty"`
|
LastModified time.Time `json:"lastmodified,omitempty" yaml:"lastmodified,omitempty"`
|
||||||
Size int64 `yaml:"size,omitempty" json:"size,omitempty"`
|
Size int64 `yaml:"size,omitempty" json:"size,omitempty"`
|
||||||
SignatureValue string `yaml:"signature,omitempty" json:"signature,omitempty"`
|
SignatureValue string `yaml:"signature,omitempty" json:"signature,omitempty"`
|
||||||
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
|
||||||
reader *transport.Reader `yaml:"-" json:"-"`
|
reader *transport.Reader `yaml:"-" json:"-"`
|
||||||
writer *transport.ReadWriter `yaml:"-" json:"-"`
|
writer *transport.ReadWriter `yaml:"-" json:"-"`
|
||||||
}
|
}
|
||||||
@ -127,10 +126,6 @@ func (h *HTTP) NormalizePath() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTP) SetResourceMapper(resources data.ResourceMapper) {
|
|
||||||
h.Resources = resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *HTTP) Open() (err error) {
|
func (h *HTTP) Open() (err error) {
|
||||||
u := h.Common.parsedURI
|
u := h.Common.parsedURI
|
||||||
if u == nil {
|
if u == nil {
|
||||||
|
@ -136,8 +136,6 @@ type Iptable struct {
|
|||||||
ReadChainCommand *command.Command `yaml:"-" json:"-"`
|
ReadChainCommand *command.Command `yaml:"-" json:"-"`
|
||||||
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
DeleteCommand *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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) SetResourceMapper(resources data.ResourceMapper) {
|
|
||||||
i.Resources = resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iptable) Clone() data.Resource {
|
func (i *Iptable) Clone() data.Resource {
|
||||||
newIpt := &Iptable {
|
newIpt := &Iptable {
|
||||||
Common: i.Common,
|
Common: i.Common,
|
||||||
@ -286,13 +280,13 @@ func (i *Iptable) Notify(m *machine.EventMessage) {
|
|||||||
// Set the chain ID and update the mapped URI
|
// Set the chain ID and update the mapped URI
|
||||||
func (i *Iptable) SetId(id uint) {
|
func (i *Iptable) SetId(id uint) {
|
||||||
if i.Id != id {
|
if i.Id != id {
|
||||||
uri := i.URI()
|
uri := folio.URI(i.URI())
|
||||||
i.Id = id
|
i.Id = id
|
||||||
decl, ok := i.Resources.Get(uri)
|
decl, ok := i.Resources.Get(uri)
|
||||||
if ok {
|
if ok {
|
||||||
i.Resources.Delete(uri)
|
i.Resources.Delete(uri)
|
||||||
}
|
}
|
||||||
i.Resources.Set(i.URI(), decl)
|
i.Resources.Set(folio.URI(i.URI()), decl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ _ "syscall"
|
|||||||
"testing"
|
"testing"
|
||||||
_ "time"
|
_ "time"
|
||||||
"decl/internal/command"
|
"decl/internal/command"
|
||||||
"decl/internal/data"
|
"decl/internal/folio"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewIptableResource(t *testing.T) {
|
func TestNewIptableResource(t *testing.T) {
|
||||||
@ -167,7 +167,7 @@ func TestIptableRuleExtractorById(t *testing.T) {
|
|||||||
|
|
||||||
func TestIptableRuleExtractorByFlags(t *testing.T) {
|
func TestIptableRuleExtractorByFlags(t *testing.T) {
|
||||||
ipt := NewIptable()
|
ipt := NewIptable()
|
||||||
ipt.Resources = data.NewResourceMapper()
|
ipt.Resources = folio.NewResourceMapper()
|
||||||
assert.NotNil(t, ipt)
|
assert.NotNil(t, ipt)
|
||||||
ipt.Table = IptableName("filter")
|
ipt.Table = IptableName("filter")
|
||||||
ipt.Chain = IptableChain("FOO")
|
ipt.Chain = IptableChain("FOO")
|
||||||
|
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,25 +9,32 @@ _ "gopkg.in/yaml.v3"
|
|||||||
_ "fmt"
|
_ "fmt"
|
||||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
|
"decl/internal/codec"
|
||||||
|
"decl/internal/transport"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MockResource struct {
|
type MockResource struct {
|
||||||
InjectURI func() string
|
*Common `json:",inline" yaml:",inline"`
|
||||||
InjectType func() string
|
InjectURI func() string
|
||||||
InjectResolveId func(ctx context.Context) string
|
InjectType func() string
|
||||||
InjectLoadDecl func(string) error
|
InjectResolveId func(ctx context.Context) string
|
||||||
InjectValidate func() error
|
InjectLoadDecl func(string) error
|
||||||
InjectApply func() error
|
InjectValidate func() error
|
||||||
InjectRead func(context.Context) ([]byte, error)
|
InjectApply func() error
|
||||||
InjectStateMachine func() machine.Stater
|
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 {
|
func (m *MockResource) Clone() data.Resource {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) StateMachine() machine.Stater {
|
func (m *MockResource) StateMachine() machine.Stater {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) SetURI(uri string) error {
|
func (m *MockResource) SetURI(uri string) error {
|
||||||
@ -35,15 +42,15 @@ func (m *MockResource) SetURI(uri string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) URI() string {
|
func (m *MockResource) URI() string {
|
||||||
return m.InjectURI()
|
return m.InjectURI()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) ResolveId(ctx context.Context) string {
|
func (m *MockResource) ResolveId(ctx context.Context) string {
|
||||||
return m.InjectResolveId(ctx)
|
return m.InjectResolveId(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) LoadDecl(yamlResourceDeclaration string) error {
|
func (m *MockResource) LoadDecl(yamlResourceDeclaration string) error {
|
||||||
return m.InjectLoadDecl(yamlResourceDeclaration)
|
return m.InjectLoadDecl(yamlResourceDeclaration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) Validate() error {
|
func (m *MockResource) Validate() error {
|
||||||
@ -51,20 +58,81 @@ func (m *MockResource) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) Apply() error {
|
func (m *MockResource) Apply() error {
|
||||||
return m.InjectApply()
|
return m.InjectApply()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) Read(ctx context.Context) ([]byte, error) {
|
func (m *MockResource) Read(ctx context.Context) ([]byte, error) {
|
||||||
return m.InjectRead(ctx)
|
return m.InjectRead(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) Type() string {
|
func (m *MockResource) Type() string {
|
||||||
return m.InjectType()
|
return m.InjectType()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockResource) UnmarshalJSON(data []byte) error {
|
func (m *MockResource) UnmarshalJSON(data []byte) error {
|
||||||
if err := json.Unmarshal(data, m); err != nil {
|
if err := json.Unmarshal(data, m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
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:"-"`
|
ReadCommand *command.Command `yaml:"-" json:"-"`
|
||||||
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
||||||
|
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNetworkRoute() *NetworkRoute {
|
func NewNetworkRoute() *NetworkRoute {
|
||||||
@ -119,10 +117,6 @@ func (n *NetworkRoute) NormalizePath() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NetworkRoute) SetResourceMapper(resources data.ResourceMapper) {
|
|
||||||
n.Resources = resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkRoute) Clone() data.Resource {
|
func (n *NetworkRoute) Clone() data.Resource {
|
||||||
newn := &NetworkRoute {
|
newn := &NetworkRoute {
|
||||||
Common: n.Common,
|
Common: n.Common,
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
ErrOpenPGPEncryptionFailure error = errors.New("OpenPGP encryption failure")
|
ErrOpenPGPEncryptionFailure error = errors.New("OpenPGP encryption failure")
|
||||||
|
ErrOpenPGPDecryptionFailure error = errors.New("OpenPGP decryption failure")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -48,14 +49,14 @@ func init() {
|
|||||||
|
|
||||||
|
|
||||||
type OpenPGPKeyRing struct {
|
type OpenPGPKeyRing struct {
|
||||||
*Common `json:",inline" yaml:",inline"`
|
*Common `json:",inline" yaml:",inline"`
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
Comment string `json:"comment,omitempty" yaml:"comment,omitempty"`
|
Comment string `json:"comment,omitempty" yaml:"comment,omitempty"`
|
||||||
Email string `json:"email,omitempty" yaml:"email,omitempty"`
|
Email string `json:"email,omitempty" yaml:"email,omitempty"`
|
||||||
KeyRing string `json:"keyring,omitempty" yaml:"keyring,omitempty"`
|
KeyRing string `json:"keyring,omitempty" yaml:"keyring,omitempty"`
|
||||||
Bits int `json:"bits" yaml:"bits"`
|
Bits int `json:"bits" yaml:"bits"`
|
||||||
KeyRingRef folio.ResourceReference `json:"keyringref,omitempty" yaml:"keyringref,omitempty"`
|
KeyRingRef folio.ResourceReference `json:"keyringref,omitempty" yaml:"keyringref,omitempty"`
|
||||||
|
|
||||||
entityList openpgp.EntityList
|
entityList openpgp.EntityList
|
||||||
}
|
}
|
||||||
@ -65,6 +66,7 @@ func NewOpenPGPKeyRing() (o *OpenPGPKeyRing) {
|
|||||||
Common: NewCommon(OpenPGPKeyRingTypeName, false),
|
Common: NewCommon(OpenPGPKeyRingTypeName, false),
|
||||||
Bits: 2048,
|
Bits: 2048,
|
||||||
}
|
}
|
||||||
|
o.Common.NormalizePath = o.NormalizePath
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +83,6 @@ func (o *OpenPGPKeyRing) NormalizePath() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) Validate() (err error) {
|
func (o *OpenPGPKeyRing) Validate() (err error) {
|
||||||
var keyringJson []byte
|
var keyringJson []byte
|
||||||
if keyringJson, err = o.JSON(); err == nil {
|
if keyringJson, err = o.JSON(); err == nil {
|
||||||
@ -124,8 +125,29 @@ func (o *OpenPGPKeyRing) EncryptPrivateKey(entity *openpgp.Entity) error {
|
|||||||
return nil
|
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) {
|
func (o *OpenPGPKeyRing) Create(ctx context.Context) (err error) {
|
||||||
var entity *openpgp.Entity
|
var entity *openpgp.Entity
|
||||||
|
var keyRingFileStream io.WriteCloser
|
||||||
|
var keyRingBuffer bytes.Buffer
|
||||||
|
|
||||||
cfg := o.Config()
|
cfg := o.Config()
|
||||||
entity, err = openpgp.NewEntity(o.Name, o.Comment, o.Email, cfg)
|
entity, err = openpgp.NewEntity(o.Name, o.Comment, o.Email, cfg)
|
||||||
o.entityList = append(o.entityList, entity)
|
o.entityList = append(o.entityList, entity)
|
||||||
@ -142,25 +164,37 @@ func (o *OpenPGPKeyRing) Create(ctx context.Context) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(o.KeyRing) == 0 {
|
if len(o.KeyRing) == 0 { // XXX this should probably always overwrite the value of KeyRing
|
||||||
var keyringBuffer bytes.Buffer
|
if privateKeyWriter, err := armor.Encode(&keyRingBuffer, openpgp.PrivateKeyType, nil); err == nil {
|
||||||
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 err = entity.SerializePrivateWithoutSigning(privateKeyWriter, nil); err == nil {
|
if err = entity.SerializePrivateWithoutSigning(privateKeyWriter, nil); err == nil {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
slog.Error("Failed writing privatekey", "err", err)
|
||||||
}
|
}
|
||||||
privateKeyWriter.Close()
|
privateKeyWriter.Close()
|
||||||
|
keyRingBuffer.WriteString("\n")
|
||||||
|
}
|
||||||
|
if publicKeyWriter, err := armor.Encode(&keyRingBuffer, openpgp.PublicKeyType, nil); err == nil {
|
||||||
|
if err = entity.Serialize(publicKeyWriter); err == nil {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
slog.Error("Failed writing public key", "err", err)
|
||||||
|
}
|
||||||
|
publicKeyWriter.Close()
|
||||||
|
keyRingBuffer.WriteString("\n")
|
||||||
}
|
}
|
||||||
keyringBuffer.WriteString("\n")
|
|
||||||
|
|
||||||
o.KeyRing = keyringBuffer.String()
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,6 +367,8 @@ func (o *OpenPGPKeyRing) ResolveId(ctx context.Context) string {
|
|||||||
if e := o.NormalizePath(); e != nil {
|
if e := o.NormalizePath(); e != nil {
|
||||||
panic(e)
|
panic(e)
|
||||||
}
|
}
|
||||||
|
o.GetIdentityFromKeyRing()
|
||||||
|
o.Common.Path = fmt.Sprintf("%s/%s/%s", o.Name, o.Comment, o.Email)
|
||||||
return o.Common.Path
|
return o.Common.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,6 +399,33 @@ func (o *OpenPGPKeyRing) ReadStat() (err error) {
|
|||||||
return
|
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) {
|
func (o *OpenPGPKeyRing) Read(ctx context.Context) (yamlData []byte, err error) {
|
||||||
var keyringReader io.ReadCloser
|
var keyringReader io.ReadCloser
|
||||||
statErr := o.ReadStat()
|
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 {
|
if keyringReader, err = o.GetContent(nil); err == nil {
|
||||||
|
defer keyringReader.Close()
|
||||||
if krData, krErr := io.ReadAll(keyringReader); krErr == nil {
|
if krData, krErr := io.ReadAll(keyringReader); krErr == nil {
|
||||||
o.KeyRing = string(krData)
|
o.KeyRing = string(krData)
|
||||||
o.entityList, err = openpgp.ReadArmoredKeyRing(strings.NewReader(o.KeyRing))
|
o.entityList, err = openpgp.ReadArmoredKeyRing(strings.NewReader(o.KeyRing))
|
||||||
} else {
|
} 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
|
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) {
|
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 o.KeyRingRef.Lookup(nil).ContentReaderStream()
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Cannot provide transport reader for string content")
|
return nil, fmt.Errorf("Cannot provide transport reader for string content")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenPGPKeyRing) ContentWriterStream() (*transport.Writer, error) {
|
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 o.KeyRingRef.Lookup(nil).ContentWriterStream()
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Cannot provide transport writer for string content")
|
return nil, fmt.Errorf("Cannot provide transport writer for string content")
|
||||||
|
@ -8,6 +8,112 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"fmt"
|
"fmt"
|
||||||
"decl/internal/data"
|
"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) {
|
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>")
|
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 (
|
var (
|
||||||
|
ErrSignatureFailedDecodingSignature error = errors.New("Failed decoding signature")
|
||||||
ErrSignatureWriterFailed error = errors.New("Failed creating signature writer")
|
ErrSignatureWriterFailed error = errors.New("Failed creating signature writer")
|
||||||
ErrArmoredWriterFailed error = errors.New("Failed to create armored 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 (
|
const (
|
||||||
@ -48,14 +51,17 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OpenPGPSignature struct {
|
type OpenPGPSignature struct {
|
||||||
*Common `json:",inline" yaml:",inline"`
|
*Common `json:"-,inline" yaml:"-,inline"`
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
Signature string `json:"signature,omitempty" yaml:"signature,omitempty"`
|
Signature string `json:"signature,omitempty" yaml:"signature,omitempty"`
|
||||||
KeyRingRef folio.ResourceReference `json:"keyringref,omitempty" yaml:"keyringref,omitempty"`
|
Signed string `json:"signed,omitempty" yaml:"signed,omitempty"`
|
||||||
SourceRef folio.ResourceReference `json:"soureref,omitempty" yaml:"sourceref,omitempty"`
|
KeyRingRef folio.Ref `json:"keyringref,omitempty" yaml:"keyringref,omitempty"`
|
||||||
SignatureRef folio.ResourceReference `json:"signatureref,omitempty" yaml:"signatureref,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
|
message *openpgp.MessageDetails
|
||||||
|
|
||||||
entityList openpgp.EntityList
|
entityList openpgp.EntityList
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,41 +96,94 @@ func (o *OpenPGPSignature) StateMachine() machine.Stater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenPGPSignature) URI() string {
|
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) {
|
func (o *OpenPGPSignature) SigningEntity() (err error) {
|
||||||
if o.KeyRingRef.IsEmpty() {
|
if len(o.entityList) < 1 {
|
||||||
var keyringConfig any
|
slog.Info("OpenPGPSignature.SigningEntity() - Loading KeyRing", "keyring", o.KeyRingRef)
|
||||||
if keyringConfig, err = o.config.GetValue("keyring"); err == nil {
|
if o.KeyRingRef.IsEmpty() {
|
||||||
o.entityList = keyringConfig.(openpgp.EntityList)
|
var keyringConfig any
|
||||||
|
if keyringConfig, err = o.config.GetValue("keyring"); err == nil {
|
||||||
|
o.entityList = keyringConfig.(openpgp.EntityList)
|
||||||
|
}
|
||||||
|
} 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ringFileStream, _ := o.KeyRingRef.Lookup(o.Resources).ContentReaderStream()
|
|
||||||
defer ringFileStream.Close()
|
|
||||||
o.entityList, err = openpgp.ReadArmoredKeyRing(ringFileStream)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (o *OpenPGPSignature) Sign(message io.Reader, w io.Writer) (err error) {
|
func (o *OpenPGPSignature) Sign(message io.Reader, w io.Writer) (err error) {
|
||||||
var writer io.WriteCloser
|
|
||||||
entity := o.entityList[0]
|
entity := o.entityList[0]
|
||||||
|
if err = openpgp.DetachSign(w, entity, message, o.Config()); err != nil {
|
||||||
if writer, err = openpgp.Sign(w, entity, nil, o.Config()); err == nil {
|
|
||||||
defer writer.Close()
|
|
||||||
_, err = io.Copy(writer, message)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("%w: %w", ErrSignatureWriterFailed, err)
|
err = fmt.Errorf("%w: %w", ErrSignatureWriterFailed, err)
|
||||||
}
|
}
|
||||||
return
|
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) {
|
func (o *OpenPGPSignature) Create(ctx context.Context) (err error) {
|
||||||
if err = o.SigningEntity(); err == nil {
|
if err = o.SigningEntity(); err == nil {
|
||||||
var sourceReadStream io.ReadCloser
|
var sourceReadStream io.ReadCloser
|
||||||
sourceReadStream, err = o.SourceRef.Lookup(o.Resources).ContentReaderStream()
|
|
||||||
|
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
|
var signatureStream, armoredWriter io.WriteCloser
|
||||||
|
|
||||||
if o.SignatureRef.IsEmpty() {
|
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 {
|
if armoredWriter, err = armor.Encode(signatureStream, openpgp.SignatureType, nil); err != nil {
|
||||||
err = fmt.Errorf("%w: %w", ErrArmoredWriterFailed, err)
|
err = fmt.Errorf("%w: %w", ErrArmoredWriterFailed, err)
|
||||||
}
|
}
|
||||||
defer armoredWriter.Close()
|
|
||||||
|
|
||||||
err = o.Sign(sourceReadStream, armoredWriter)
|
err = o.Sign(sourceReadStream, armoredWriter)
|
||||||
|
armoredWriter.Close()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -196,8 +255,10 @@ func (o *OpenPGPSignature) Notify(m *machine.EventMessage) {
|
|||||||
_ = o.AddError(triggerErr)
|
_ = o.AddError(triggerErr)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
slog.Info("OpenPGPSignature.Notify()", "dest", "start_read", "err", readErr)
|
||||||
_ = o.AddError(readErr)
|
_ = o.AddError(readErr)
|
||||||
if o.IsResourceInconsistent() {
|
if o.IsResourceInconsistent() {
|
||||||
|
slog.Info("OpenPGPSignature.Notify()", "dest", "read-failed", "machine", o.StateMachine())
|
||||||
if triggerErr := o.StateMachine().Trigger("read-failed"); triggerErr == nil {
|
if triggerErr := o.StateMachine().Trigger("read-failed"); triggerErr == nil {
|
||||||
panic(readErr)
|
panic(readErr)
|
||||||
} else {
|
} else {
|
||||||
@ -320,16 +381,16 @@ func (o *OpenPGPSignature) ResolveId(ctx context.Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenPGPSignature) GetContentSourceRef() string {
|
func (o *OpenPGPSignature) GetContentSourceRef() string {
|
||||||
return string(o.SignatureRef)
|
return o.SignatureRef.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenPGPSignature) SetContentSourceRef(uri 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) {
|
func (o *OpenPGPSignature) SignatureRefStat() (info fs.FileInfo, err error) {
|
||||||
err = fmt.Errorf("%w: %s", ErrResourceStateAbsent, o.SignatureRef)
|
err = fmt.Errorf("%w: %s", ErrResourceStateAbsent, o.SignatureRef.Uri)
|
||||||
if len(o.SignatureRef) > 0 {
|
if len(o.SignatureRef.Uri) > 0 {
|
||||||
rs, _ := o.ContentReaderStream()
|
rs, _ := o.ContentReaderStream()
|
||||||
defer rs.Close()
|
defer rs.Close()
|
||||||
return rs.Stat()
|
return rs.Stat()
|
||||||
@ -346,6 +407,16 @@ func (o *OpenPGPSignature) ReadStat() (err error) {
|
|||||||
return err
|
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 {
|
func (o *OpenPGPSignature) Update(ctx context.Context) error {
|
||||||
return o.Create(ctx)
|
return o.Create(ctx)
|
||||||
}
|
}
|
||||||
@ -354,7 +425,69 @@ func (o *OpenPGPSignature) Delete(ctx context.Context) error {
|
|||||||
return os.Remove(o.Common.Path)
|
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)
|
return yaml.Marshal(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,21 +10,208 @@ import (
|
|||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/folio"
|
"decl/internal/folio"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
|
"decl/internal/ext"
|
||||||
"log"
|
"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) {
|
var mockKeyRingPassphraseConfig MockConfigValueGetter = func(key string) (any, error) {
|
||||||
uri := "openpgp-keyring://TestUser1/TestUser1/testuser@rosskeen.house"
|
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 := folio.NewDeclaration()
|
||||||
keyRingDecl.NewResource(&uri)
|
keyRingDecl.NewResource(&uri)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
declarationAttributes := fmt.Sprintf(`
|
declarationAttributes := fmt.Sprintf(`
|
||||||
name: TestUser1
|
name: %s
|
||||||
comment: TestUser1
|
comment: %s
|
||||||
email: testuser@rosskeen.house
|
email: %s
|
||||||
keyringref: file://%s/keyring.asc
|
keyringref: file://%s/keyring_%s.asc
|
||||||
`, string(TempDir))
|
`, 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()
|
testKeyRing := keyRingDecl.Resource()
|
||||||
if e := testKeyRing.LoadString(declarationAttributes, codec.FormatYaml); e != nil {
|
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)
|
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)
|
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) {
|
func TestNewOpenPGPSignatureResource(t *testing.T) {
|
||||||
assert.NotNil(t, NewOpenPGPSignature())
|
assert.NotNil(t, NewOpenPGPSignature())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateSignature(t *testing.T) {
|
func TestSigningEntity(t *testing.T) {
|
||||||
ctx := context.Background()
|
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()
|
testUserKeyRingResource := folio.Dereference(keyRingUri, m)
|
||||||
|
assert.NotNil(t, testUserKeyRingResource)
|
||||||
assert.Nil(t, TempDir.CreateFile("test.txt", "test data"))
|
assert.NotNil(t, testUserKeyRingResource.Resource().(*OpenPGPKeyRing).entityList[0].PrivateKey)
|
||||||
|
|
||||||
declarationAttributes := fmt.Sprintf(`
|
declarationAttributes := fmt.Sprintf(`
|
||||||
keyringref: %s
|
keyringref:
|
||||||
sourceref: file://%s/test.txt
|
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))
|
`, string(keyRingUri), string(TempDir))
|
||||||
|
|
||||||
testSignature := NewOpenPGPSignature()
|
testSignature := NewOpenPGPSignature()
|
||||||
testSignature.Resources = m
|
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)
|
assert.Nil(t, e)
|
||||||
|
|
||||||
err := testSignature.Create(ctx)
|
err := testSignature.Create(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
/*
|
|
||||||
assert.Greater(t, len(testSignature.entityList), 0)
|
assert.Greater(t, len(testSignature.entityList), 0)
|
||||||
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser1 (TestUser1) <testuser@rosskeen.house>")
|
assert.Contains(t, testSignature.entityList[0].Identities, "TestUser3 (TestUser3) <testuser3@rosskeen.house>")
|
||||||
assert.Contains(t, testSignature.Signature, "-----END PGP PUBLIC KEY BLOCK-----")
|
assert.NotContains(t, testSignature.Signature, "-----END PGP PUBLIC KEY BLOCK-----")
|
||||||
assert.Contains(t, testSignature.Signature, "-----END PGP PRIVATE 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) {
|
func TestReadSignature(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
declarationAttributes := `
|
assert.Nil(t, TempDir.CreateFile("read-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/read-test.txt
|
||||||
signature: |-
|
signature: |-
|
||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
mQGNBGctCH8BDADGmdabVG6gDuSRk0razrEMEproTMT5w9zUMWH5uUeLY9eM9g5S
|
iQEzBAABCgAdFiEEAM6cDwBqHcVfKX+b3weSNhbTZdcFAmhQtmMACgkQ3weSNhbT
|
||||||
/5I062ume5jj6MIC1lq7tqJXh3Zwcv7Lf7ER1SWa1h6BGruHaF4o9GiR01FHfyVM
|
ZddNPAf/ZSKVOUXVlIBgXMsw2RqVWYkOzyuWADeOq6P1UHlRN0xxSmOFnHukxstL
|
||||||
YTMTkMxFi1wnY87Mr0f+EIv1i9u2nD1o9moBXzEXT0JFFGyla8DvnblQhLhrwfNl
|
zTa7KSpf5y3/DKRSTWldAy8WYFr0mx5hoDy5c83OYZo2oUtFsVYifol4JfZeF+ij
|
||||||
lN0L2LQJDTVnrPj4eMaFshqP2UdqNiYjR2qfLyCH/ZZhxg++G2KJhNzlkOzqZZJk
|
6a2trW17VjtKYzwpSTt/SAaTxKj/6oVevpWykOPfg6V3CCLfnVEuBMhotU91ngbg
|
||||||
iYwfEUvGg/PzdCsSOYEvSureI0bF1hKBGK+RpOY0sKcvSY0xiY1YXEzJSau5cnFe
|
wh3R3K7LrVVJ6kYI4RKUeBIx280JZMG0sZ/AuwUdWG6KFavGMbHVzHHD2PMwT53t
|
||||||
/mdwC7YleZiKsGOyBsbRFn7FUXM4eM7CkDISjZMmKDBzbvzgFXsUG2upgC+B7fvi
|
xJ5kzWyS9ZWws8r/B33GM9szHplXulhETsd1S3x0/R2MoVcHeAaAa6JwD0y0Kxu6
|
||||||
pTpgQxQk1Xi3+4bNQmgupJEUrP0XlIFoBVJdoTb0wcs8JUNDfc6ESZB+eA1OJdR+
|
MuQM3twP32lK4UUqNx4oPHzU5gbirA==
|
||||||
xiag1XwN3PKcwzmoZoZ71oT/eqAOufwhWXFJ+StPqwd+BVpK1YwbG0jRagNlM/29
|
=+dVO
|
||||||
+Rzh2A70qwCcCXcAEQEAAbQwVGVzdFVzZXIgKFRlc3RVc2VyKSA8bWF0dGhld3Jp
|
-----END PGP SIGNATURE-----
|
||||||
Y2guY29uZkBnbWFpbC5jb20+iQHOBBMBCgA4FiEErhhqUPYtSfwcGHCb+/Evfjwu
|
` , string(keyRingUri), string(TempDir))
|
||||||
gEkFAmctCH8CGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQ+/EvfjwugElu
|
|
||||||
cwv/ZB9xlMf8C9CBOVX8nvU1HiWvmJqlvcePobQBc7Y54pWuK+giv+/SE3bEx6x/
|
|
||||||
Vb0aWrJ52CBod6R1YfyPW+F58W9kADIPFRkH/bXExj+WMrXZU4J8Gz5nCxECK6PB
|
|
||||||
CR8xh/T9lbvDt1q7JeP4+ldzZJoSLxAK6D5EeYTC8OKXVMuTgHBmwtiTC+Hyja3+
|
|
||||||
HV1MZwx7SnnXmX5dRtPq8z1F1shoM4UTLEaolA6r3XQKwfsP9c6LS2VUc+Yft4eN
|
|
||||||
6JCz9+fa/N9bMgIS6Az23JDYJWynmmPx82Y/uqiSxXL9qljOUsgR/QK9OaLL8fFH
|
|
||||||
UD6Ob+TnjH/cPBoESXrslFcwKZWMsAxJ9w6K/HJT+Fm+8XcbN3awoXcEtLAeKirL
|
|
||||||
z7borUsseCXJqY4epHfbvhx7NjhxElspY2A51l6oX4OoVyFL3163anxwzEEXgMRk
|
|
||||||
+pPGlzw55cq/iN48qURetgs94Vdr4HCNJFY8+CLUyNqPQHaVXA6nUndL2wqfOqwj
|
|
||||||
82R0uQGNBGctCH8BDAC/uHoD/vw8dOQt/cHyObaAEunN3Xy2MOtpY7TRh9AdrNKY
|
|
||||||
O0hEFQvllf8iEzW4WjiIXCzNyWzY53AD6k1kWg5tW0/6hLxk9YMUnUhi6MSD17zj
|
|
||||||
QQMR8XRUNuadVh8G0INJnvXVhgJXSQmKCn+4e6e1/gYKvHq9uEYf4N1BSazlCH/e
|
|
||||||
ZEhHTzI8WLtZeG+rM1wBW/3KuRrLDP9WUHamzp+0bL5OKvEhetZQZQxPr9wYccAh
|
|
||||||
bPU9MeatkAn6CwbeCOxUGUbwC0rzMVL3CPvOjhPFWGJaqi4H4ZdSSKN/vceXyfWh
|
|
||||||
CvzzJR/v0jzwJaE6sxIdIu1ylRKXN+hZ7Eqn7ZDurWgVxAH9o0jXkBNGsmZlqdRx
|
|
||||||
J+86/aGpSlNXZZO6o4xznV9Xd8ssuvwMLKN3qwVYEcbFOTdgeRw8dJo8fx4Y14tZ
|
|
||||||
RQUVPLh2iI4ykjFnBJFfOExAEKHQauLgQ6iXRsetgTb5RvUevOvIOJJTZGrqrhxt
|
|
||||||
7lHYlDfxS7zJL9ygldMAEQEAAYkBtgQYAQoAIBYhBK4YalD2LUn8HBhwm/vxL348
|
|
||||||
LoBJBQJnLQh/AhsMAAoJEPvxL348LoBJ+5oMALOv9RIyhNvyeJ4y7TLpOervh/0C
|
|
||||||
EfvIxYEVtDTFZlqfkuovhF1Cbgu+PP9iG2JU0FYHsNisf+1XSMKHX0DIm9gWWZaZ
|
|
||||||
J1CElJ4vsQ0t/4ypSrP7cZB6FokrQBcglpB9mVg0meVzCmZOJfVL+s+gCycshSZR
|
|
||||||
msw9Y3tN72JMAGdxHXtr1DTL3uDbl12Bz+egYNrqmugX9Jc9HiWG51XO9SDtztG0
|
|
||||||
KtVLcBA6X4Avc940Q4d4BofmOT4ajAAnysnR84UvTTSaAr9m/xvyKNEuS4YLysaC
|
|
||||||
gOG8nDFxujEkfO5FW+N1r5hFd2owt8Ige4e59wPRu5RVycPF3+JnxM70wFxQPkO3
|
|
||||||
lDtVTMG9vZyRkxRyKeqFo0z4msbc9WHwdvI6l/h7h2v6E6VbMe2sX/k+CxNyTPBX
|
|
||||||
sn7sjApKUjVpdXtHbu81ELhAbVPJPpMlkTdUwUUUfPD7uBoyRQbEQwgpbPQrEqmE
|
|
||||||
+aAQq8u60fWheEIG+xaV3T01zrNRUo6I7xu5kA==
|
|
||||||
=yFbn
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
||||||
`
|
|
||||||
|
|
||||||
testSignature := NewOpenPGPSignature()
|
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)
|
e := testSignature.LoadDecl(declarationAttributes)
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
y, err := testSignature.Read(ctx)
|
y, err := testSignature.Read(ctx)
|
||||||
@ -136,7 +504,175 @@ func TestReadSignature(t *testing.T) {
|
|||||||
assert.NotNil(t, y)
|
assert.NotNil(t, y)
|
||||||
|
|
||||||
assert.Greater(t, len(testSignature.entityList), 0)
|
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:"-"`
|
ReadCommand *command.Command `yaml:"-" json:"-"`
|
||||||
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
||||||
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -123,10 +122,6 @@ func (p *Package) Init(u data.URIParser) error {
|
|||||||
return p.SetParsedURI(u)
|
return p.SetParsedURI(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Package) SetResourceMapper(resources data.ResourceMapper) {
|
|
||||||
p.Resources = resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Package) Clone() data.Resource {
|
func (p *Package) Clone() data.Resource {
|
||||||
newp := &Package {
|
newp := &Package {
|
||||||
Common: p.Common.Clone(),
|
Common: p.Common.Clone(),
|
||||||
|
@ -88,7 +88,6 @@ type PKI struct {
|
|||||||
|
|
||||||
Bits int `json:"bits" yaml:"bits"`
|
Bits int `json:"bits" yaml:"bits"`
|
||||||
EncodingType EncodingType `json:"type" yaml:"type"`
|
EncodingType EncodingType `json:"type" yaml:"type"`
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPKI() *PKI {
|
func NewPKI() *PKI {
|
||||||
@ -96,11 +95,6 @@ func NewPKI() *PKI {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *PKI) SetResourceMapper(resources data.ResourceMapper) {
|
|
||||||
slog.Info("PKI.SetResourceMapper()", "resources", resources)
|
|
||||||
k.Resources = resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *PKI) Clone() data.Resource {
|
func (k *PKI) Clone() data.Resource {
|
||||||
return &PKI {
|
return &PKI {
|
||||||
Common: k.Common.Clone(),
|
Common: k.Common.Clone(),
|
||||||
|
@ -12,32 +12,15 @@ _ "gopkg.in/yaml.v3"
|
|||||||
_ "log"
|
_ "log"
|
||||||
_ "os"
|
_ "os"
|
||||||
"decl/internal/transport"
|
"decl/internal/transport"
|
||||||
"decl/internal/ext"
|
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
"decl/internal/folio"
|
"decl/internal/folio"
|
||||||
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"path/filepath"
|
"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)
|
type StringContentReadWriter func() (any, error)
|
||||||
|
|
||||||
func (s StringContentReadWriter) ContentWriterStream() (*transport.Writer, 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) URI() (string) { return "" }
|
||||||
func (s StringContentReadWriter) Validate() (error) { return nil }
|
func (s StringContentReadWriter) Validate() (error) { return nil }
|
||||||
func (s StringContentReadWriter) ResourceType() data.TypeName { return "" }
|
func (s StringContentReadWriter) ResourceType() data.TypeName { return "" }
|
||||||
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) {
|
func TestNewPKIKeysResource(t *testing.T) {
|
||||||
r := NewPKI()
|
r := NewPKI()
|
||||||
@ -90,29 +93,19 @@ func TestPKIEncodeKeys(t *testing.T) {
|
|||||||
r.PublicKey()
|
r.PublicKey()
|
||||||
assert.NotNil(t, r.publicKey)
|
assert.NotNil(t, r.publicKey)
|
||||||
|
|
||||||
r.Resources = TestResourceMapper(func(key string) (data.Declaration, bool) {
|
r.Resources = folio.NewResourceMapper()
|
||||||
switch key {
|
|
||||||
case "buffer://privatekey":
|
privateKeyBuffer := folio.NewDeclaration()
|
||||||
return StringContentReadWriter(func() (any, error) {
|
privateKeyBuffer.Attributes = NewMockBufferResource("privatekey", &privateTarget)
|
||||||
w := &transport.Writer{}
|
r.Resources.Set("buffer://privatekey", privateKeyBuffer)
|
||||||
w.SetStream(ext.WriteNopCloser(&privateTarget))
|
|
||||||
return w, nil
|
publicKeyBuffer := folio.NewDeclaration()
|
||||||
}), true
|
publicKeyBuffer.Attributes = NewMockBufferResource("publickey", &publicTarget)
|
||||||
case "buffer://publickey":
|
r.Resources.Set("buffer://publickey", publicKeyBuffer)
|
||||||
return StringContentReadWriter(func() (any, error) {
|
|
||||||
w := &transport.Writer{}
|
certKeyBuffer := folio.NewDeclaration()
|
||||||
w.SetStream(ext.WriteNopCloser(&publicTarget))
|
certKeyBuffer.Attributes = NewMockBufferResource("certificate", &certTarget)
|
||||||
return w, nil
|
r.Resources.Set("buffer://certificate", certKeyBuffer)
|
||||||
}), 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.PrivateKeyRef = folio.ResourceReference("buffer://privatekey")
|
r.PrivateKeyRef = folio.ResourceReference("buffer://privatekey")
|
||||||
r.PublicKeyRef = folio.ResourceReference("buffer://publickey")
|
r.PublicKeyRef = folio.ResourceReference("buffer://publickey")
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "certificate",
|
"title": "certificate",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [],
|
||||||
"properties": {
|
"properties": {
|
||||||
"publickey": {
|
"publickey": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -15,8 +16,8 @@
|
|||||||
},
|
},
|
||||||
"SerialNumber": {
|
"SerialNumber": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Serial number",
|
"description": "Serial number",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
"Issuer": {
|
"Issuer": {
|
||||||
"$ref": "pkixname.schema.json"
|
"$ref": "pkixname.schema.json"
|
||||||
|
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#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "document",
|
"title": "document",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [],
|
||||||
],
|
|
||||||
"properties": {
|
"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": {
|
"configurations": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Configurations list",
|
"description": "Configurations list",
|
||||||
@ -31,6 +48,7 @@
|
|||||||
{ "$ref": "iptable-declaration.schema.json" },
|
{ "$ref": "iptable-declaration.schema.json" },
|
||||||
{ "$ref": "network-route-declaration.schema.json" },
|
{ "$ref": "network-route-declaration.schema.json" },
|
||||||
{ "$ref": "openpgp-keyring-declaration.schema.json" },
|
{ "$ref": "openpgp-keyring-declaration.schema.json" },
|
||||||
|
{ "$ref": "openpgp-signature-declaration.schema.json" },
|
||||||
{ "$ref": "package-declaration.schema.json" },
|
{ "$ref": "package-declaration.schema.json" },
|
||||||
{ "$ref": "pki-declaration.schema.json" },
|
{ "$ref": "pki-declaration.schema.json" },
|
||||||
{ "$ref": "user-declaration.schema.json" }
|
{ "$ref": "user-declaration.schema.json" }
|
||||||
|
@ -10,10 +10,22 @@
|
|||||||
"description": "Resource type name.",
|
"description": "Resource type name.",
|
||||||
"enum": [ "file" ]
|
"enum": [ "file" ]
|
||||||
},
|
},
|
||||||
|
"transition": {
|
||||||
|
"$ref": "storagetransition.schema.json"
|
||||||
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Config name"
|
"description": "Config name"
|
||||||
},
|
},
|
||||||
|
"onerror": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "error handling strategy",
|
||||||
|
"enum": [
|
||||||
|
"stop",
|
||||||
|
"fail",
|
||||||
|
"skip"
|
||||||
|
]
|
||||||
|
},
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"$ref": "file.schema.json"
|
"$ref": "file.schema.json"
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"path": {
|
"path": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "file path",
|
"description": "file path",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
"owner": {
|
"owner": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Resource type name.",
|
"description": "Resource type name.",
|
||||||
"enum": [ "file" ]
|
"enum": [ "openpgp-keyring" ]
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "string",
|
"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:"-"`
|
ReadCommand *command.Command `json:"-" yaml:"-"`
|
||||||
UpdateCommand *command.Command `json:"-" yaml:"-"`
|
UpdateCommand *command.Command `json:"-" yaml:"-"`
|
||||||
DeleteCommand *command.Command `json:"-" yaml:"-"`
|
DeleteCommand *command.Command `json:"-" yaml:"-"`
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUser() *User {
|
func NewUser() *User {
|
||||||
@ -107,10 +106,6 @@ func (u *User) NormalizePath() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) SetResourceMapper(resources data.ResourceMapper) {
|
|
||||||
u.Resources = resources
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) Clone() data.Resource {
|
func (u *User) Clone() data.Resource {
|
||||||
newu := &User {
|
newu := &User {
|
||||||
Common: u.Common,
|
Common: u.Common,
|
||||||
|
@ -20,10 +20,10 @@ type Schema struct {
|
|||||||
schema gojsonschema.JSONLoader
|
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)
|
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)}
|
//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)
|
slog.Info("schema error", "source", source, "schema", s.schema, "result", result, "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
slog.Info("schema.Validate()", "schema", s.schema, "result", result, "err", err)
|
|
||||||
|
|
||||||
if !result.Valid() {
|
if !result.Valid() {
|
||||||
schemaErrors := strings.Builder{}
|
schemaErrors := strings.Builder{}
|
||||||
|
schemaErrors.WriteString(fmt.Sprintf("source: %s schema: %#v ", source, s.schema))
|
||||||
for _, err := range result.Errors() {
|
for _, err := range result.Errors() {
|
||||||
schemaErrors.WriteString(err.String() + "\n")
|
schemaErrors.WriteString(err.String() + "\n")
|
||||||
}
|
}
|
||||||
@ -56,3 +56,11 @@ func (s *Schema) ValidateSchema() error {
|
|||||||
slog.Info("validate schema definition", "schemaloader", sl, "err", schemaErr)
|
slog.Info("validate schema definition", "schemaloader", sl, "err", schemaErr)
|
||||||
return 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