github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/depends/conf/app/app_ctx.go (about) 1 package app 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "os" 8 "path" 9 "path/filepath" 10 "reflect" 11 "strings" 12 13 "github.com/fatih/color" 14 "github.com/pkg/errors" 15 "github.com/spf13/cobra" 16 "gopkg.in/yaml.v2" 17 18 "github.com/machinefi/w3bstream/pkg/depends/base/consts" 19 "github.com/machinefi/w3bstream/pkg/depends/base/types" 20 "github.com/machinefi/w3bstream/pkg/depends/conf/deploy" 21 "github.com/machinefi/w3bstream/pkg/depends/conf/env" 22 "github.com/machinefi/w3bstream/pkg/depends/x/misc/must" 23 "github.com/machinefi/w3bstream/pkg/depends/x/reflectx" 24 ) 25 26 type Ctx struct { 27 cmd *cobra.Command 28 name string // name app name 29 feat string // feat git feature 30 version string // version git version|git tag 31 root string // root app root 32 vars []*env.Vars // vars default env vars 33 conf []reflect.Value // conf config reflect.Value 34 deployers map[string]deploy.Deployer 35 ctx context.Context 36 } 37 38 func New(setters ...OptSetter) *Ctx { 39 c := &Ctx{ctx: context.Background()} 40 for _, setter := range setters { 41 setter(c) 42 } 43 c.cmd = &cobra.Command{} 44 if feat, ok := os.LookupEnv(consts.EnvProjectFeat); ok && feat != "" { 45 c.feat = feat 46 } 47 if version, ok := os.LookupEnv(consts.EnvProjectVersion); ok && version != "" { 48 c.version = version 49 } 50 if name, ok := os.LookupEnv(consts.EnvProjectName); ok && name != "" { 51 c.name = name 52 } 53 _ = os.Setenv(consts.EnvProjectName, c.name) 54 return c 55 } 56 57 func (c *Ctx) Context() context.Context { return c.ctx } 58 59 // Conf init all configs from yml file, and do initialization for each config. 60 // config dir include `config.yml.template` `config.yml` and `master.yml` 61 // config.yml.template shows config file template and preset config values 62 // config.yml contains all user configured values 63 func (c *Ctx) Conf(configs ...interface{}) { 64 local, err := os.ReadFile(filepath.Join(c.root, "./config/config.yml")) 65 if err == nil { 66 kv := make(map[string]string) 67 log.Println("set config to to environ") 68 if err = yaml.Unmarshal(local, &kv); err == nil { 69 for k, v := range kv { 70 _ = os.Setenv(k, v) 71 log.Println("set config", k, v) 72 } 73 } 74 } 75 76 if key := os.Getenv(consts.GoRuntimeEnv); key == "" { 77 _ = os.Setenv(consts.GoRuntimeEnv, consts.ProduceEnv) 78 } 79 80 for _, v := range configs { 81 rv := reflect.ValueOf(v) 82 if rv.Kind() != reflect.Ptr { 83 panic("should pass pointer for setting value") 84 } 85 86 must.NoError(c.scan(rv)) 87 must.NoError(c.marshal(rv)) 88 } 89 90 if err = c.MarshalDefault(); err != nil { 91 panic(err) 92 } 93 94 for _, v := range configs { 95 rv := reflect.ValueOf(v) 96 c.conf = append(c.conf, rv) 97 98 if zero, ok := v.(types.ZeroChecker); ok && zero.IsZero() { 99 t := reflect.Indirect(reflect.ValueOf(v)).Type() 100 log.Println(errors.Errorf( 101 "zero config: %s", 102 color.CyanString("[%s.%s]:", filepath.Base(t.PkgPath()), t.Name()), 103 )) 104 } 105 106 switch conf := v.(type) { 107 case interface{ Init() }: 108 conf.Init() 109 case interface{ Init() error }: 110 if err = conf.Init(); err != nil { 111 panic(errors.Errorf("conf init: %v", err)) 112 } 113 } 114 115 rv = reflectx.Indirect(rv) 116 if rv.Kind() == reflect.Struct { 117 for i := 0; i < rv.NumField(); i++ { 118 value := rv.Field(i) 119 if !value.CanInterface() { 120 continue 121 } 122 if value.Type().Kind() == reflect.Interface { 123 panic("interface type unsupported in config scanning") 124 } 125 fv := value.Interface() 126 ft := reflect.Indirect(reflect.ValueOf(fv)).Type() 127 if zero, ok := fv.(types.ZeroChecker); ok && zero.IsZero() { 128 log.Println(errors.Errorf( 129 "zero config: %s", 130 color.CyanString("[%s.%s]:", filepath.Base(ft.PkgPath()), ft.Name()), 131 )) 132 continue 133 } 134 switch conf := value.Interface().(type) { 135 case interface{ Init() }: 136 conf.Init() 137 case interface{ Init() error }: 138 if err = conf.Init(); err != nil { 139 panic(errors.Errorf("init failed %s %s", 140 color.CyanString("[%s.%s]:", filepath.Base(ft.PkgPath()), ft.Name()), 141 color.RedString("[%v]", err), 142 )) 143 } 144 } 145 } 146 } 147 } 148 } 149 150 func (c *Ctx) AddCommand(name string, fn func(...string), commands ...func(*cobra.Command)) { 151 cmd := &cobra.Command{Use: name} 152 153 for i := range commands { 154 commands[i](cmd) 155 } 156 157 cmd.Run = func(_ *cobra.Command, args []string) { 158 fn(args...) 159 } 160 161 c.cmd.AddCommand(cmd) 162 } 163 164 func (c *Ctx) String() string { 165 ret := c.name 166 if c.feat != "" { 167 ret += "--" + c.feat 168 } 169 if c.version != "" { 170 ret += "@" + c.version 171 } 172 return ret 173 } 174 175 func (c *Ctx) Root() string { return c.root } 176 177 func (c *Ctx) Execute(fn func(...string), commands ...func(*cobra.Command)) { 178 for i := range commands { 179 commands[i](c.cmd) 180 } 181 c.cmd.Use = c.name 182 c.cmd.Version = c.version 183 c.cmd.Run = func(cmd *cobra.Command, args []string) { 184 for i := range c.conf { 185 c.log(c.conf[i]) 186 } 187 fn(args...) 188 } 189 // TODO implement app deploy config generator 190 // for name, dpl := range c.deployers { 191 // c.AddCommand(name, func(...string) { 192 // if setter, ok := dpl.(types.DefaultSetter); ok { 193 // setter.SetDefault() 194 // } 195 // filename := path.Join(c.root, name) 196 // if err := dpl.Write(filename); err != nil { 197 // panic(fmt.Errorf("init %s error: %v", name, err)) 198 // } 199 // }, func(cmd *cobra.Command) { 200 // cmd.Short = "init configuration for " + name 201 // }) 202 // } 203 if err := c.cmd.Execute(); err != nil { 204 panic(err) 205 } 206 } 207 208 func (c *Ctx) scan(rv reflect.Value) error { 209 vars := env.NewVars(c.group(rv)) 210 211 if err := env.NewDecoder(vars).Decode(rv); err != nil { 212 return err 213 } 214 c.vars = append(c.vars, vars) 215 if _, err := env.NewEncoder(vars).Encode(rv); err != nil { 216 return err 217 } 218 return nil 219 } 220 221 func (c *Ctx) marshal(rv reflect.Value) error { 222 log.Println("load environments .....") 223 vars := env.LoadVarsFromEnviron(c.group(rv), os.Environ()) 224 if err := env.NewDecoder(vars).Decode(rv); err != nil { 225 return err 226 } 227 return nil 228 } 229 230 func (c *Ctx) MarshalDefault() error { 231 // TODO: add comment for each single config element 232 m := map[string]string{ 233 consts.GoRuntimeEnv: consts.ProduceEnv, 234 } 235 for _, vars := range c.vars { 236 for _, v := range vars.Values { 237 if !v.Optional { 238 m[v.Key(vars.Prefix)] = v.Value 239 } 240 } 241 } 242 243 return WriteYamlFile(path.Join(c.root, "./config/config.yml.template"), m) 244 } 245 246 func (c *Ctx) log(rv reflect.Value) { 247 vars := env.NewVars(c.group(rv)) 248 if _, err := env.NewEncoder(vars).Encode(rv); err != nil { 249 panic(err) 250 } 251 fmt.Printf("%s", string(vars.MaskBytes())) 252 } 253 254 type Marshaller func(v interface{}) ([]byte, error) 255 256 // group returns config group name 257 func (c *Ctx) group(rv reflect.Value) string { 258 group := rv.Elem().Type().Name() 259 if rv.Elem().Type().Implements(types.RTypeNamed) { 260 group = rv.Elem().Interface().(types.Named).Name() 261 } 262 if group == "" { 263 return strings.ToUpper(strings.Replace(c.name, "-", "_", -1)) 264 } 265 return strings.ToUpper(strings.Replace(c.name+"__"+group, "-", "_", -1)) 266 }