Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: tools-2692 add metadata to config files #24

Merged
merged 9 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions asconf/metadata/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package metadata

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

var commentChar string
var findComments *regexp.Regexp

func init() {
commentChar = "#"
findComments = regexp.MustCompile(commentChar + `(?m)\s*(.+):\s*(.+)\s*$`)
dwelch-spike marked this conversation as resolved.
Show resolved Hide resolved
}

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)
}
})
}
}
52 changes: 46 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 @@ -39,7 +40,9 @@ func newConvertCmd() *cobra.Command {
Short: "Convert between yaml and Aerospike config format.",
Long: `Convert is used to convert between yaml and aerospike configuration
files. Input files are converted to their opposite format, yaml -> conf, conf -> yaml.
Specifying the server version that will use the aerospike.conf is required.
Specifying the Aerospike Database version that will use the aerospike.conf is required unless
version metadata is present in the file or the --force option is used.
Aerospike Database metadata is written to files generated by asconfig.
dwelch-spike marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -51,7 +54,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 @@ -99,6 +105,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 @@ -124,6 +139,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 @@ -188,16 +214,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 All @@ -220,7 +260,7 @@ func newConvertCmd() *cobra.Command {

// flags and configuration settings
// aerospike-version is marked required in this cmd's PreRun if the --force flag is not provided
res.Flags().StringP("aerospike-version", "a", "", "Aerospike server version for the configuration file. Ex: 6.2.0.\nThe first 3 digits of the Aerospike version number are required.\nThis option is required unless --force is used")
res.Flags().StringP("aerospike-version", "a", "", "Aerospike server version for the configuration file. Ex: 6.2.0.\nThe first 3 digits of the Aerospike version number are required.\nThis option is required unless --force is used or the configuration file being converted contains Aerospike Database version metadata.")
dwelch-spike marked this conversation as resolved.
Show resolved Hide resolved
res.Flags().BoolP("force", "f", false, "Override checks for supported server version and config validation")
res.Flags().StringP("output", "o", os.Stdout.Name(), "File path to write output to")

Expand Down
Loading