Burada config adında özel bir struct etiketi oluşturacağız ve bu etiketin sadece oynamak için kind|path isminde iki alanı olacak.


Örnek senaryomuz, uygulama sırları ve düz yapılandırma değerleri ile çalışmakla ilgilidir. Burada "secret" alanların değerini geçersiz kılacağız ve "plain" alanları olduğu gibi bırakacağız. Bu sadece gösteri amaçlıdır. Gerçek hayatta her alan için AWS Secrets Manager/Parameter Store, HashiCorp Vault vb. gibi bir dahili veya harici depolama hizmetini kullanarak "path" değeri ile gerçek değerleri bulabilirsiniz. Sistemi nasıl tasarlayacağınız tamamen size kalmış. Bu sadece bir fikir, bu yüzden onu yeniden düzenlemekten veya benimsemekten çekinmeyin. Farklı bir konuyla ilgili olsa da "path" için environment.service.***, environment/service/***, service/environment/*** gibi bir sözdizimini veya başka bir şeyi kullanabilirsiniz. Sözdizimi, kullanacağınız hizmet tarafından da dikte edilebilir.


config.go


package config

import (
"fmt"
"reflect"
)

// Bind iterates through all the fields in the config and operates only on
// custom "config" tag as long as it matches certain criteria.
func Bind(cfg interface{}) error {
configSource := reflect.ValueOf(cfg)
if configSource.Kind() != reflect.Ptr {
return fmt.Errorf("config must be a pointer")
}
configSource = configSource.Elem()
if configSource.Kind() != reflect.Struct {
return fmt.Errorf("config must be a struct")
}

configType := configSource.Type()

for i := 0; i < configSource.NumField(); i++ {
fieldTag, ok := configType.Field(i).Tag.Lookup("config")
if !ok {
continue
}

fieldName := configType.Field(i).Name
fieldValue := configSource.FieldByName(fieldName)
if !fieldValue.IsValid() {
continue
}
if !fieldValue.CanSet() {
continue
}

tagKind, tagPath := tag(fieldTag)
if tagKind == "" && tagPath == "" {
continue
}

if tagKind == tagSecret {
// This is just a random act. You should handle it as per your setup.
// Also add other type cases as well
switch configSource.Field(i).Kind() {
case reflect.String:
fieldValue.SetString("***")
case reflect.Int:
fieldValue.SetInt(000)
}
}
}

return nil
}

tag.go


package config

import (
"strings"
)

const (
tagPlain = "plain"
tagSecret = "secret"
)

// tag extracts kind and path values from the incoming tag value. Both values are
// must be non-empty string otherwise an empty string is returned for both.
func tag(tag string) (string, string) {
tagParts := strings.Split(tag, ",")
if len(tagParts) == 0 || len(tagParts) != 2 {
return "", ""
}

kindParts := strings.Split(tagParts[0], "=")
if len(kindParts) == 0 || len(kindParts) != 2 {
return "", ""
}
if kindParts[0] != "kind" || (kindParts[1] != tagPlain && kindParts[1] != tagSecret) {
return "", ""
}

pathParts := strings.Split(tagParts[1], "=")
if len(pathParts) == 0 || len(pathParts) != 2 {
return "", ""
}
if pathParts[0] != "path" || pathParts[1] == "" {
return "", ""
}

return kindParts[1], pathParts[1]
}

main.go


package main

import (
"fmt"
"log"
"time"

"you/config"
)

type Config struct {
Shutdown time.Duration
PrivateKey string `config:"kind=secret,path=common.ssh.private_key"`
Password string `config:"kind=secret,path=team.login.password"`
YearFound int `config:"kind=plain,path=team.year_found"`
}

func main() {
cfg := Config{
Shutdown: time.Minute,
PrivateKey: "prv",
Password: "psw",
YearFound: 2021,
}

fmt.Printf("ORIGINAL: %+v\n", cfg)

if err := config.Bind(&cfg); err != nil {
log.Fatalln(err)
}

fmt.Printf("MODIFIED: %+v\n", cfg)
}

Test


$ go run main.go 
ORIGINAL: {Shutdown:1m0s PrivateKey:prv Password:psw YearFound:2021}
MODIFIED: {Shutdown:1m0s PrivateKey:*** Password:*** YearFound:2021}