Skip to content

Commit

Permalink
feat: add config package & optimize dir package (#90)
Browse files Browse the repository at this point in the history
Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
  • Loading branch information
JeyJeyGao authored Aug 2, 2022
1 parent 34be24d commit ba141e1
Show file tree
Hide file tree
Showing 12 changed files with 650 additions and 51 deletions.
47 changes: 47 additions & 0 deletions config/base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package config

import (
"encoding/json"
"os"
"path/filepath"

"github.com/notaryproject/notation-go/dir"
)

var (
// ConfigPath is the path for config.json
ConfigPath string
// SigningKeysPath is the path for signingkeys.json
SigningKeysPath string
)

func init() {
ConfigPath = dir.Path.Config()
SigningKeysPath = dir.Path.SigningKeyConfig()
}

// save stores the cfg struct to file
func save(filePath string, cfg interface{}) error {
dir := filepath.Dir(filePath)
if err := os.MkdirAll(dir, 0700); err != nil {
return err
}
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
return encoder.Encode(cfg)
}

// load reads file, parses json and stores in cfg struct
func load(filePath string, cfg interface{}) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
return json.NewDecoder(file).Decode(cfg)
}
56 changes: 56 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package config

import (
"errors"
"io/fs"
)

// CertificateReference is a named file path.
type CertificateReference struct {
Name string `json:"name"`
Path string `json:"path"`
}

// Is checks whether the given name is equal with the Name variable
func (c CertificateReference) Is(name string) bool {
return c.Name == name
}

// Config reflects the config.json file.
// Specification: https://github.com/notaryproject/notation/pull/76
type Config struct {
VerificationCertificates VerificationCertificates `json:"verificationCerts"`
InsecureRegistries []string `json:"insecureRegistries"`
CredentialsStore string `json:"credsStore,omitempty"`
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
}

// VerificationCertificates is a collection of public certs used for verification.
type VerificationCertificates struct {
Certificates []CertificateReference `json:"certs"`
}

// NewConfig creates a new config file
func NewConfig() *Config {
return &Config{
InsecureRegistries: []string{},
}
}

// Save stores the config to file
func (c *Config) Save() error {
return save(ConfigPath, c)
}

// LoadConfig reads the config from file or return a default config if not found.
func LoadConfig() (*Config, error) {
var config Config
err := load(ConfigPath, &config)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return NewConfig(), nil
}
return nil, err
}
return &config, nil
}
91 changes: 91 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package config

import (
"path/filepath"
"reflect"
"testing"

"github.com/notaryproject/notation-go/dir"
)

const (
configPath = "./testdata/config.json"
nonexistentPath = "./testdata/nonexistent.json"
)

var sampleConfig = &Config{
VerificationCertificates: VerificationCertificates{
Certificates: []CertificateReference{
{
Name: "wabbit-networks",
Path: "/home/demo/.config/notation/certificate/wabbit-networks.crt",
},
{
Name: "import.acme-rockets",
Path: "/home/demo/.config/notation/certificate/import.acme-rockets.crt",
},
},
},
InsecureRegistries: []string{
"registry.wabbit-networks.io",
},
}

func TestLoadFile(t *testing.T) {
t.Cleanup(func() {
// restore path
ConfigPath = dir.Path.Config()
})
type args struct {
filePath string
}
tests := []struct {
name string
args args
want *Config
wantErr bool
}{
{
name: "load config file",
args: args{filePath: configPath},
want: sampleConfig,
wantErr: false,
},
{
name: "load default config file",
args: args{filePath: nonexistentPath},
want: NewConfig(),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ConfigPath = tt.args.filePath
got, err := LoadConfig()
if (err != nil) != tt.wantErr {
t.Errorf("loadFile() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("loadFile() = %v, want %v", got, tt.want)
}
})
}
}

func TestSaveFile(t *testing.T) {
t.Cleanup(func() {
// restore path
ConfigPath = dir.Path.Config()
})
root := t.TempDir()
ConfigPath = filepath.Join(root, "config.json")
sampleConfig.Save()
config, err := LoadConfig()
if err != nil {
t.Fatal("Load config file from temp dir failed")
}
if !reflect.DeepEqual(sampleConfig, config) {
t.Fatal("save config file failed.")
}
}
63 changes: 63 additions & 0 deletions config/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package config

import (
"errors"
"io/fs"
)

// X509KeyPair contains the paths of a public/private key pair files.
type X509KeyPair struct {
KeyPath string `json:"keyPath,omitempty"`
CertificatePath string `json:"certPath,omitempty"`
}

// ExternalKey contains the necessary information to delegate
// the signing operation to the named plugin.
type ExternalKey struct {
ID string `json:"id,omitempty"`
PluginName string `json:"pluginName,omitempty"`
PluginConfig map[string]string `json:"pluginConfig,omitempty"`
}

// KeySuite is a named key suite.
type KeySuite struct {
Name string `json:"name"`

*X509KeyPair
*ExternalKey
}

// Is checks whether the given name is equal with the Name variable
func (k KeySuite) Is(name string) bool {
return k.Name == name
}

// SigningKeys reflects the signingkeys.json file.
type SigningKeys struct {
Default string `json:"default"`
Keys []KeySuite `json:"keys"`
}

// Save config to file
func (s *SigningKeys) Save() error {
return save(SigningKeysPath, s)
}

// NewSigningKeys creates a new signingkeys config file
func NewSigningKeys() *SigningKeys {
return &SigningKeys{Keys: []KeySuite{}}
}

// LoadSigningKeys reads the config from file
// or return a default config if not found.
func LoadSigningKeys() (*SigningKeys, error) {
var config SigningKeys
err := load(SigningKeysPath, &config)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return NewSigningKeys(), nil
}
return nil, err
}
return &config, nil
}
100 changes: 100 additions & 0 deletions config/keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package config

import (
"path/filepath"
"reflect"
"testing"

"github.com/notaryproject/notation-go/dir"
)

const (
signingKeysPath = "./testdata/signingkeys.json"
)

var sampleSigningKeysInfo = &SigningKeys{
Default: "wabbit-networks",
Keys: []KeySuite{
{
Name: "wabbit-networks",
X509KeyPair: &X509KeyPair{
KeyPath: "/home/demo/.config/notation/localkeys/wabbit-networks.key",
CertificatePath: "/home/demo/.config/notation/localkeys/wabbit-networks.crt",
},
},
{
Name: "import.acme-rockets",
X509KeyPair: &X509KeyPair{
KeyPath: "/home/demo/.config/notation/localkeys/import.acme-rockets.key",
CertificatePath: "/home/demo/.config/notation/localkeys/import.acme-rockets.crt",
},
},
{
Name: "external-key",
ExternalKey: &ExternalKey{

ID: "id1",
PluginName: "pluginX",
PluginConfig: map[string]string{
"key": "value",
},
},
},
},
}

func TestLoadSigningKeysInfo(t *testing.T) {
t.Cleanup(func() {
// restore path
SigningKeysPath = dir.Path.SigningKeyConfig()
})
type args struct {
filePath string
}
tests := []struct {
name string
args args
want *SigningKeys
}{
{
name: "read signingkeys info",
args: args{filePath: signingKeysPath},
want: sampleSigningKeysInfo,
},
{
name: "get default signingkeys info",
args: args{filePath: nonexistentPath},
want: NewSigningKeys(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
SigningKeysPath = tt.args.filePath
got, err := LoadSigningKeys()
if err != nil {
t.Errorf("LoadSigningKeysInfo() error = %v", err)
return
}
if !reflect.DeepEqual(tt.want, got) {
t.Fatal("singingKeysInfo test failed.")
}
})
}
}

func TestSaveSigningKeys(t *testing.T) {
t.Cleanup(func() {
// restore path
SigningKeysPath = dir.Path.SigningKeyConfig()
})
root := t.TempDir()
SigningKeysPath = filepath.Join(root, "signingkeys.json")
sampleSigningKeysInfo.Save()
info, err := LoadSigningKeys()
if err != nil {
t.Fatal("Load signingkeys.json from temp dir failed.")
}
if !reflect.DeepEqual(sampleSigningKeysInfo, info) {
t.Fatal("Save signingkeys.json failed.")
}
}
17 changes: 17 additions & 0 deletions config/testdata/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"verificationCerts": {
"certs": [
{
"name": "wabbit-networks",
"path": "/home/demo/.config/notation/certificate/wabbit-networks.crt"
},
{
"name": "import.acme-rockets",
"path": "/home/demo/.config/notation/certificate/import.acme-rockets.crt"
}
]
},
"insecureRegistries": [
"registry.wabbit-networks.io"
]
}
Loading

0 comments on commit ba141e1

Please sign in to comment.