Skip to content

Commit

Permalink
feat: tools-2692 add metadata to config files (#24)
Browse files Browse the repository at this point in the history
* feat: tools-2692 add metadata to config files
  • Loading branch information
dwelch-spike authored Nov 16, 2023
1 parent f7e7e88 commit 08095d9
Show file tree
Hide file tree
Showing 11 changed files with 603 additions and 15 deletions.
60 changes: 60 additions & 0 deletions asconf/metadata/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package metadata

import (
"fmt"
"regexp"
"sort"
)

var commentChar string
var findComments *regexp.Regexp

func init() {
commentChar = "#"
// findComments matches text of the form `<commentChar> <key>: <val>`
// for example, parsing...
// # comment about metadata
// # a: b
// other data
// matches
// # a: b
findComments = regexp.MustCompile(commentChar + `(?m)\s*(.+):\s*(.+)\s*$`)
}

func Unmarshal(src []byte, dst map[string]string) error {
matches := findComments.FindAllSubmatch(src, -1)

for _, match := range matches {
// 0 index is entire line
k := match[1]
v := match[2]
// only save the first occurrence of k
if _, ok := dst[string(k)]; !ok {
dst[string(k)] = string(v)
}
}

return nil
}

func formatLine(k string, v any) string {
fmtStr := "%s %s: %v"
return fmt.Sprintf(fmtStr, commentChar, k, v)
}

func Marshal(src map[string]string) ([]byte, error) {
res := []byte{}
lines := make([]string, len(src))

for k, v := range src {
lines = append(lines, formatLine(k, v)+"\n")
}

sort.Strings(lines)

for _, v := range lines {
res = append(res, []byte(v)...)
}

return res, nil
}
193 changes: 193 additions & 0 deletions asconf/metadata/metadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
//go:build unit
// +build unit

package metadata_test

import (
"reflect"
"testing"

"github.com/aerospike/asconfig/asconf/metadata"
)

var testBasic = `
# comment about metadata
# a: b
other data
`

var testConf = `
# comment about metadata
# aerospike-server-version: 6.4.0.1
# asconfig-version: 0.12.0
# asadm-version: 2.20.0
#
logging {
file /dummy/file/path2 {
context any info # aerospike-server-version: collide
}
}
`

var testConfNoMeta = `
namespace ns2 {
replication-factor 2
memory-size 8G
index-type shmem # comment mid config
sindex-type shmem
storage-engine memory
}
# comment
`

var testConfPartialMeta = `
namespace ns1 {
replication-factor 2
memory-size 4G
index-type flash {
mount /dummy/mount/point1 /test/mount2
mounts-high-water-pct 30
mounts-size-limit 10G
}
# comment about metadata
# aerospike-server-version: 6.4.0.1
# other-item: a long value
`

func TestUnmarshal(t *testing.T) {
type args struct {
src []byte
dst map[string]string
}
tests := []struct {
name string
args args
want map[string]string
wantErr bool
}{
{
name: "t1",
args: args{
src: []byte(testConf),
dst: map[string]string{},
},
want: map[string]string{
"aerospike-server-version": "6.4.0.1",
"asadm-version": "2.20.0",
"asconfig-version": "0.12.0",
},
wantErr: false,
},
{
name: "t2",
args: args{
src: []byte(testConfNoMeta),
dst: map[string]string{},
},
want: map[string]string{},
wantErr: false,
},
{
name: "t3",
args: args{
src: []byte(testConfPartialMeta),
dst: map[string]string{},
},
want: map[string]string{
"aerospike-server-version": "6.4.0.1",
"other-item": "a long value",
},
wantErr: false,
},
{
name: "t4",
args: args{
src: []byte(testBasic),
dst: map[string]string{},
},
want: map[string]string{
"a": "b",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := metadata.Unmarshal(tt.args.src, tt.args.dst); (err != nil) != tt.wantErr {
t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(tt.args.dst, tt.want) {
t.Errorf("Unmarshal() = %v, want %v", tt.args.dst, tt.want)
}
})
}
}

var testMarshalMetaComplete = `# aerospike-server-version: 7.0.0.0
# asadm-version: 2.20.0
# asconfig-version: 0.12.0
`

var testMarshalMetaNone = ""

var testMarshalMetaPartial = `# aerospike-server-version: 6.4.0
`

func TestMarshalText(t *testing.T) {
type args struct {
src map[string]string
}
tests := []struct {
name string
args args
want []byte
wantErr bool
}{
{
name: "t1",
args: args{
src: map[string]string{
"aerospike-server-version": "7.0.0.0",
"asadm-version": "2.20.0",
"asconfig-version": "0.12.0",
},
},
want: []byte(testMarshalMetaComplete),
wantErr: false,
},
{
name: "t2",
args: args{
src: map[string]string{},
},
want: []byte(testMarshalMetaNone),
wantErr: false,
},
{
name: "t3",
args: args{
src: map[string]string{
"aerospike-server-version": "6.4.0",
},
},
want: []byte(testMarshalMetaPartial),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := metadata.Marshal(tt.args.src)
if (err != nil) != tt.wantErr {
t.Errorf("Marshal() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Marshal() = %v, want %v", got, tt.want)
}
})
}
}
51 changes: 45 additions & 6 deletions cmd/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/aerospike/aerospike-management-lib/asconfig"
"github.com/aerospike/asconfig/asconf"
"github.com/aerospike/asconfig/asconf/metadata"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -40,8 +41,9 @@ func newConvertCmd() *cobra.Command {
Long: `Convert is used to convert between yaml and aerospike configuration
files. Input files are converted to their opposite format, yaml -> conf, conf -> yaml.
The convert command validates the configuration file for compatibility with the Aerospike
version passed to the --aerospike-version option, unless the --force option is used.
Specifying the server version that will use the aerospike.conf is required.
version passed to the --aerospike-version option, unless
version metadata is present in the file or the --force option is used.
Aerospike Database metadata is written to files generated by asconfig.
Usage examples...
convert local file "aerospike.yaml" to aerospike config format for version 6.2.0 and
write it to local file "aerospike.conf."
Expand All @@ -53,7 +55,10 @@ func newConvertCmd() *cobra.Command {
Normally the file format is inferred from file extensions ".yaml" ".conf" etc.
Source format can be forced with the --format flag.
Ex: asconfig convert -a "6.2.0" --format yaml example_file
If a file path is not provided, asconfig reads the file contents from stdin.`,
If a file path is not provided, asconfig reads the file contents from stdin.
Ex: asconfig convert -a "6.4.0"
If the file has been converted by asconfig before, the --aerospike-version option is not needed.
Ex: asconfig convert -a "6.4.0" aerospike.yaml | asconfig convert --format conf`,
RunE: func(cmd *cobra.Command, args []string) error {
logger.Debug("Running convert command")

Expand Down Expand Up @@ -101,6 +106,15 @@ func newConvertCmd() *cobra.Command {
return fmt.Errorf("%w: %s", errInvalidFormat, srcFormat)
}

// if the version option is empty,
// try populating from the metadata
if version == "" {
version, err = getMetaDataItem(fdata, metaKeyAerospikeVersion)
if err != nil {
return errors.Join(errMissingAerospikeVersion, err)
}
}

conf, err := asconf.NewAsconf(
fdata,
srcFormat,
Expand All @@ -126,6 +140,17 @@ func newConvertCmd() *cobra.Command {
return err
}

// prepend metadata to the config output
mtext, err := genMetaDataText(metaDataArgs{
src: fdata,
aerospikeVersion: version,
asconfigVersion: VERSION,
})
if err != nil {
return err
}
out = append(mtext, out...)

outputPath, err := cmd.Flags().GetString("output")
if err != nil {
return err
Expand Down Expand Up @@ -190,16 +215,30 @@ func newConvertCmd() *cobra.Command {
return err
}

if !force {
cmd.MarkFlagRequired("aerospike-version")
cfgData, err := os.ReadFile(args[0])
if err != nil {
return err
}

metaData := map[string]string{}
metadata.Unmarshal(cfgData, metaData)

// if the aerospike server version is in the cfg file's
// metadata, don't mark --aerospike-version as required
var aeroVersionRequired bool
if _, ok := metaData[metaKeyAerospikeVersion]; !ok {
if !force {
cmd.MarkFlagRequired("aerospike-version")
aeroVersionRequired = true
}
}

av, err := cmd.Flags().GetString("aerospike-version")
if err != nil {
return err
}

if !force {
if aeroVersionRequired {
if av == "" {
return errors.Join(errMissingAerospikeVersion, err)
}
Expand Down
Loading

0 comments on commit 08095d9

Please sign in to comment.