diff --git a/pkg/config/generator.go b/pkg/config/generator.go new file mode 100644 index 0000000..fc6ca55 --- /dev/null +++ b/pkg/config/generator.go @@ -0,0 +1,45 @@ +package config + +// FileContentGenerator +// we can use this interface to generate file config content in config map +// and use GenerateAllFile function to generate configMap data +type FileContentGenerator interface { + Generate() string + FileName() string +} + +// EnvGenerator +// we can use this interface to generate env config in config map +// and use GenerateAllEnv function to generate configMap data +type EnvGenerator interface { + Generate() map[string]string +} + +// Parser +// config content parser, can get config content by golang-template or create config content self(customize) +// see template_parser.go +type Parser interface { + Parse() (string, error) +} + +func GenerateAllFile(confGenerator []FileContentGenerator) map[string]string { + data := make(map[string]string) + for _, generator := range confGenerator { + if generator.Generate() != "" { + data[generator.FileName()] = generator.Generate() + } + } + return data +} + +func GenerateAllEnv(confGenerator []EnvGenerator) map[string]string { + data := make(map[string]string) + for _, generator := range confGenerator { + if generator.Generate() != nil { + for k, v := range generator.Generate() { + data[k] = v + } + } + } + return data +} diff --git a/pkg/config/template_parser.go b/pkg/config/template_parser.go new file mode 100644 index 0000000..d2ea597 --- /dev/null +++ b/pkg/config/template_parser.go @@ -0,0 +1,28 @@ +package config + +import ( + "bytes" + ctrl "sigs.k8s.io/controller-runtime" + "text/template" +) + +var logging = ctrl.Log.WithName("template-parser") + +type TemplateParser struct { + Value interface{} + Template string +} + +func (t *TemplateParser) Parse() (string, error) { + temp, err := template.New("").Parse(t.Template) + if err != nil { + logging.Error(err, "failed to parse template", "template", t.Template) + return t.Template, err + } + var b bytes.Buffer + if err := temp.Execute(&b, t.Value); err != nil { + logging.Error(err, "failed to execute template", "template", t.Template, "data", t.Value) + return t.Template, err + } + return b.String(), nil +} diff --git a/pkg/config/template_parser_test.go b/pkg/config/template_parser_test.go new file mode 100644 index 0000000..f30c113 --- /dev/null +++ b/pkg/config/template_parser_test.go @@ -0,0 +1,76 @@ +package config + +import "testing" + +const log4jProperties = `log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +{{- if .LogDir}} +log4j.appender.stdout.File={{.LogDir}}/{{.LogName}}.log +{{- else}} +log4j.appender.stdout.File=logs/app.log +{{- end}} +log4j.appender.stdout.Threshold=INFO +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +{{ range .Loggers }} +log4j.logger.{{.Logger}}={{.Level}} +{{- end}} +` + +func TestTemplateParser_Parse(t1 *testing.T) { + type fields struct { + Value interface{} + Template string + } + tests := []struct { + name string + fields fields + want string + wantErr bool + }{ + // TODO: Add test cases. + { + name: "parse template simplely", + fields: fields{ + Value: map[string]interface{}{ + "LogDir": "customLogs", + "LogName": "customApp", + "Loggers": []map[string]string{ + {"Logger": "test1", "Level": "WARN"}, + {"Logger": "test2", "Level": "DEBUG"}, + }, + }, + Template: log4jProperties, + }, + want: `log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.File=customLogs/customApp.log +log4j.appender.stdout.Threshold=INFO +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n + +log4j.logger.test1=WARN +log4j.logger.test2=DEBUG +`, + wantErr: false, + }, + } + for _, tt := range tests { + t1.Run(tt.name, func(t1 *testing.T) { + t := &TemplateParser{ + Value: tt.fields.Value, + Template: tt.fields.Template, + } + got, err := t.Parse() + if (err != nil) != tt.wantErr { + t1.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t1.Errorf("Parse() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/util/configuration.go b/pkg/util/configuration.go new file mode 100644 index 0000000..80dbc02 --- /dev/null +++ b/pkg/util/configuration.go @@ -0,0 +1,124 @@ +package util + +import ( + "bufio" + "fmt" + "strings" +) + +type NameValuePair struct { + comment string + Name string + Value string +} + +// MakeConfigFileContent returns the content of a configuration file +// content such as: +// ``` +// key1 value1 +// key2 value2 +// ``` +func MakeConfigFileContent(config map[string]string) string { + content := "" + if len(config) == 0 { + return content + } + for k, v := range config { + content += fmt.Sprintf("%s %s\n", k, v) + } + return content +} + +// MakePropertiesFileContent returns the content of a properties file +// content such as: +// ```properties +// key1=value1 +// key2=value2 +// ``` +func MakePropertiesFileContent(config map[string]string) string { + content := "" + if len(config) == 0 { + return content + } + for k, v := range config { + content += fmt.Sprintf("%s=%s\n", k, v) + } + return content +} + +func OverrideConfigFileContent(current string, override string) string { + if current == "" { + return override + } + if override == "" { + return current + } + return current + "\n" + override +} + +// OverridePropertiesFileContent use bufio resolve properties +func OverridePropertiesFileContent(current string, override []NameValuePair) (string, error) { + var properties []NameValuePair + //scan current + if err := ScanProperties(current, &properties); err != nil { + logger.Error(err, "failed to scan current properties") + return "", err + } + // override + OverrideProperties(override, &properties) + + // to string + var res string + for _, v := range properties { + res += fmt.Sprintf("%s%s=%s\n", v.comment, v.Name, v.Value) + } + return res, nil +} + +func ScanProperties(current string, properties *[]NameValuePair) error { + scanner := bufio.NewScanner(strings.NewReader(current)) + + var comment string + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "#") || len(line) == 0 { + comment += line + "\n" + continue + } + + items := strings.Split(line, "=") + if len(items) == 2 { + *properties = append(*properties, NameValuePair{ + comment: comment, + Name: items[0], + Value: items[1], + }) + comment = "" + } else { + return fmt.Errorf("invalid property line: %s", line) + } + } + return scanner.Err() +} + +func OverrideProperties(override []NameValuePair, current *[]NameValuePair) { + if len(override) == 0 { + return + } + var currentKeys = make(map[string]int) + for i, v := range *current { + currentKeys[v.Name] = i + } + + for _, v := range override { + if _, ok := currentKeys[v.Name]; ok { + (*current)[currentKeys[v.Name]].Value = v.Value // override + } else { + // append new + *current = append(*current, NameValuePair{ + Name: v.Name, + Value: v.Value, + }) + } + } +} diff --git a/pkg/util/configuration_test.go b/pkg/util/configuration_test.go new file mode 100644 index 0000000..531bf40 --- /dev/null +++ b/pkg/util/configuration_test.go @@ -0,0 +1,53 @@ +package util + +import ( + "strings" + "testing" +) + +func TestOverrideXmlFileContent(t *testing.T) { + const origin = ` + + + key1 + value1 + + ` + + type args struct { + current string + override map[string]string + } + tests := []struct { + name string + args args + want string + }{ + // TODO: Add test cases. + { + name: "test1", + args: args{ + current: origin, + override: map[string]string{"key2": "value2"}, + }, + want: ` + + + key1 + value1 + + + key2 + value2 + +`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := OverrideXmlContent(tt.args.current, tt.args.override); strings.TrimSpace(got) != strings.TrimSpace(tt.want) { + t.Errorf("OverrideXmlContent() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/util/name_util.go b/pkg/util/name.go similarity index 100% rename from pkg/util/name_util.go rename to pkg/util/name.go diff --git a/pkg/util/reconciler_util.go b/pkg/util/reconciler.go similarity index 100% rename from pkg/util/reconciler_util.go rename to pkg/util/reconciler.go diff --git a/pkg/util/security_util.go b/pkg/util/security.go similarity index 100% rename from pkg/util/security_util.go rename to pkg/util/security.go diff --git a/pkg/util/security_util_test.go b/pkg/util/security_test.go similarity index 100% rename from pkg/util/security_util_test.go rename to pkg/util/security_test.go diff --git a/pkg/util/xml.go b/pkg/util/xml.go new file mode 100644 index 0000000..62d95e6 --- /dev/null +++ b/pkg/util/xml.go @@ -0,0 +1,117 @@ +package util + +import ( + "bytes" + "encoding/xml" +) + +type XmlNameValuePair struct { + Name string `xml:"name"` + Value string `xml:"value"` +} + +type XmlConfiguration struct { + XMLName xml.Name `xml:"configuration"` + Properties []XmlNameValuePair `xml:"property"` +} + +func NewXmlConfiguration(properties []XmlNameValuePair) *XmlConfiguration { + return &XmlConfiguration{ + Properties: properties, + } +} + +func (c *XmlConfiguration) String(properties []XmlNameValuePair) string { + if len(c.Properties) != 0 { + c.Properties = c.DistinctProperties(properties) + } + buf := new(bytes.Buffer) + if _, err := buf.WriteString("\n"); err != nil { + logger.Error(err, "failed to write xml document head") + } + enc := xml.NewEncoder(buf) + enc.Indent("", " ") + if err := enc.Encode(c); err != nil { + logger.Error(err, "failed to encode xml document") + panic(err) + } + return buf.String() +} + +// DistinctProperties distinct properties by name, +func (c *XmlConfiguration) DistinctProperties(properties []XmlNameValuePair) []XmlNameValuePair { + var collect []XmlNameValuePair + collect = append(collect, c.Properties...) + collect = append(collect, properties...) + + var distinctProperties []XmlNameValuePair + var distinctKeys map[string]int + for idx, v := range collect { + if distinctKeys == nil { + distinctKeys = make(map[string]int) + } + if existIdx, ok := distinctKeys[v.Name]; !ok { + distinctKeys[v.Name] = idx + distinctProperties = append(distinctProperties, v) + } else { + distinctProperties[existIdx] = v + } + } + return distinctProperties + + //var distinctMap = make(map[string]XmlNameValuePair) + //for _, v := range collect { + // distinctMap[v.Name] = v + //} + //return maps.Values(distinctMap) +} + +func (c *XmlConfiguration) StringWithProperties(properties map[string]string) string { + var pairs []XmlNameValuePair + for k, v := range properties { + pairs = append(pairs, XmlNameValuePair{ + Name: k, + Value: v, + }) + } + return c.String(pairs) +} + +// Append to exist xml dom +func Append(originXml string, properties []XmlNameValuePair) string { + var xmlDom XmlConfiguration + //string -> dom + if err := xml.Unmarshal([]byte(originXml), &xmlDom); err != nil { + panic(err) + } + return xmlDom.String(properties) +} + +// OverrideXmlContent overrides the content of a xml file +// append the override properties to the current xml dom +func OverrideXmlContent(current string, overrideProperties map[string]string) string { + var xmlDom XmlConfiguration + //string -> dom + if err := xml.Unmarshal([]byte(current), &xmlDom); err != nil { + panic(err) + } + // do override + for k, v := range overrideProperties { + overridePair := XmlNameValuePair{ + Name: k, + Value: v, + } + xmlDom.Properties = append(xmlDom.Properties, overridePair) + } + // dom -> string + var b bytes.Buffer + if _, err := b.WriteString("\n"); err != nil { + logger.Error(err, "failed to write string") + } + encoder := xml.NewEncoder(&b) + encoder.Indent("", " ") + if err := encoder.Encode(xmlDom); err != nil { + logger.Error(err, "failed to encode xml") + } + return b.String() +} diff --git a/pkg/util/xml_test.go b/pkg/util/xml_test.go new file mode 100644 index 0000000..98cbb9b --- /dev/null +++ b/pkg/util/xml_test.go @@ -0,0 +1,71 @@ +package util + +import ( + "testing" +) + +func TestXmlConfiguration_Append(t *testing.T) { + + const origin = ` + + + key1 + value1 + + ` + + type args struct { + originXml string + properties []XmlNameValuePair + } + tests := []struct { + name string + args args + want string + }{ + // TODO: Add test cases. + { + name: "append a new property", + args: args{ + originXml: origin, + properties: []XmlNameValuePair{{Name: "key2", Value: "value2"}}, + }, + want: ` + + + key1 + value1 + + + key2 + value2 + +`, + }, + { + name: "append a new property, but the name exists in origin xml, should override it", + args: args{ + originXml: origin, + properties: []XmlNameValuePair{{Name: "key1", Value: "value2"}, {Name: "key3", Value: "value3"}}, + }, + want: ` + + + key1 + value2 + + + key3 + value3 + +`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Append(tt.args.originXml, tt.args.properties); got != tt.want { + t.Errorf("XmlConfiguration.Append() = %v, want %v", got, tt.want) + } + }) + } +}