trpc.group/trpc-go/trpc-cmdline@v1.0.9/config/config.go (about) 1 // Tencent is pleased to support the open source community by making tRPC available. 2 // 3 // Copyright (C) 2023 THL A29 Limited, a Tencent company. 4 // All rights reserved. 5 // 6 // If you have downloaded a copy of the tRPC source code from Tencent, 7 // please note that tRPC source code is licensed under the Apache 2.0 License, 8 // A copy of the Apache 2.0 License is included in this file. 9 10 // Package config provides configuration-related capabilities for this project. 11 package config 12 13 import ( 14 "bytes" 15 "errors" 16 "fmt" 17 "math/rand" 18 "os" 19 "os/user" 20 "path/filepath" 21 "sync" 22 23 "github.com/spf13/viper" 24 "gopkg.in/yaml.v2" 25 26 "trpc.group/trpc-go/trpc-cmdline/bindata/compress" 27 "trpc.group/trpc-go/trpc-cmdline/gobin" 28 "trpc.group/trpc-go/trpc-cmdline/util/fs" 29 "trpc.group/trpc-go/trpc-cmdline/util/log" 30 ) 31 32 // LanguageTemplates is the templates for each programming language. 33 type LanguageTemplates map[string]*Template 34 35 var once sync.Once 36 var globalConfig Config 37 38 func init() { 39 // Auto initialize. 40 if _, err := Init(); err != nil { 41 log.Error("init error: %+v", err) 42 } 43 } 44 45 type options struct { 46 force bool 47 } 48 49 // Option is the option provided for config.Init. 50 type Option func(*options) 51 52 // WithForce sets whether to force initialize the asset files, i.e. extract the files 53 // inside the binary to overwrite the existing files located at $HOME/.trpc-cmdline-assets. 54 // Default is false. 55 func WithForce(force bool) Option { 56 return func(o *options) { 57 o.force = force 58 } 59 } 60 61 // Init initializes the configuration. 62 // If the configuration file trpc.yaml is missing, or the code template is missing, 63 // or the configuration version does not match, the configuration is automatically installed. 64 // After the initialization is successful, returns the path of config file's directory. 65 func Init(opts ...Option) (string, error) { 66 o := &options{} 67 for _, opt := range opts { 68 opt(o) 69 } 70 // List of candidate template paths. 71 paths, err := candidatedInstallPath() 72 if err != nil { 73 return "", fmt.Errorf("get template search path error: %w", err) 74 } 75 76 // Check if the candidate template path exists. 77 var reinstall bool 78 installPath, err := templateInstallPath(paths) 79 if err != nil { 80 if err != errTemplateNotFound { 81 return "", fmt.Errorf("get template instal path from %+v error: %w", paths, err) 82 } 83 reinstall = true 84 installPath = paths[0] 85 } else { 86 // Version mismatch, needs to be reinstalled. 87 dat, err := os.ReadFile(filepath.Join(installPath, "VERSION")) 88 if err != nil || string(dat) != TRPCCliVersion { 89 reinstall = true 90 } 91 } 92 // Install the template as needed. 93 if o.force || reinstall { 94 log.Debug("reinstall template to %s", installPath) 95 if err := installTemplate(installPath); err != nil { 96 return "", fmt.Errorf("install template to %s error: %w", installPath, err) 97 } 98 } 99 return installPath, nil 100 } 101 102 // CurrentTemplatePath gets the installation path of the trpc configuration file, 103 // which is installed to $HOME/.trpc-cmdline-assets. 104 func CurrentTemplatePath() (dir string, err error) { 105 candicates, err := candidatedInstallPath() 106 if err != nil { 107 return "", err 108 } 109 return templateInstallPath(candicates) 110 } 111 112 var errTemplateNotFound = errors.New("Template directory not found") 113 114 func candidatedInstallPath() ([]string, error) { 115 u, err := user.Current() 116 if err != nil { 117 return nil, err 118 } 119 120 candidates := []string{filepath.Join(u.HomeDir, ".trpc-cmdline-assets")} 121 return candidates, nil 122 } 123 124 func templateInstallPath(dirs []string) (dir string, err error) { 125 for _, d := range dirs { 126 if fin, err := os.Lstat(d); err == nil && fin.IsDir() { 127 return d, nil 128 } 129 } 130 return "", errTemplateNotFound 131 } 132 133 func installTemplate(installTo string) error { 134 tmp := filepath.Join(os.TempDir(), "trpc"+fmt.Sprintf("tmp+%d", rand.Uint64())) 135 if err := os.RemoveAll(installTo); err != nil { 136 return fmt.Errorf("remove %s err: %w", installTo, err) 137 } 138 if err := os.RemoveAll(tmp); err != nil { 139 return fmt.Errorf("remove %s err: %w", tmp, err) 140 } 141 if err := os.MkdirAll(tmp, os.ModePerm); err != nil { 142 return fmt.Errorf("mkdir err: %w", err) 143 } 144 if err := compress.Untar(tmp, bytes.NewBuffer(gobin.AssetsGo)); err != nil { 145 return fmt.Errorf("untar to %s err: %w", tmp, err) 146 } 147 if err := fs.Move(tmp, installTo); err != nil { 148 return fmt.Errorf("move %s -> %s err: %w", tmp, installTo, err) 149 } 150 if err := os.RemoveAll(tmp); err != nil { 151 return fmt.Errorf("remove %s after move err: %w", tmp, err) 152 } 153 return nil 154 } 155 156 // GlobalConfig returns global config. 157 func GlobalConfig() Config { 158 once.Do(func() { 159 cfg, err := LoadConfig() 160 if err != nil { 161 panic(err) 162 } 163 globalConfig = *cfg 164 }) 165 return globalConfig 166 } 167 168 // GetTemplate returns the corresponding configuration information 169 // based on the given index of the serialization protocol and the language type. 170 func GetTemplate(idl IDLType, lang string) (*Template, error) { 171 if !idl.Valid() { 172 return nil, fmt.Errorf("invalid idltype: %s", idl) 173 } 174 175 tpl, ok := GlobalConfig().Templates[idl.String()][lang] 176 if !ok { 177 return nil, fmt.Errorf("language: %s not supported", lang) 178 } 179 return tpl, nil 180 } 181 182 func repair(cfg *Config) error { 183 installedPath, err := CurrentTemplatePath() 184 if err != nil { 185 return err 186 } 187 188 for _, tpls := range cfg.Templates { 189 for lang, tpl := range tpls { 190 tpl.AssetDir = filepath.Join(installedPath, tpl.AssetDir) 191 if tpl.Language == "" { 192 tpl.Language = lang 193 } 194 if tpl.LangFileExt == "" { 195 tpl.LangFileExt = lang 196 } 197 } 198 } 199 return nil 200 } 201 202 // LoadConfig loads trpc config. 203 func LoadConfig() (cfg *Config, err error) { 204 defer func() { 205 if err == nil && cfg != nil { 206 repair(cfg) 207 } 208 }() 209 // Try to load configuration from the file specified by --config flag. 210 fp := viper.ConfigFileUsed() 211 if fp != "" { 212 return loadConfigFile(fp) 213 } 214 215 // Try to load configuration from the template installation path. 216 d, err := CurrentTemplatePath() 217 if err != nil { 218 return nil, err 219 } 220 221 fp = filepath.Join(d, "trpc.yaml") 222 return loadConfigFile(fp) 223 } 224 225 func loadConfigFile(fp string) (*Config, error) { 226 b, err := os.ReadFile(fp) 227 if err != nil { 228 return nil, err 229 } 230 231 cfg := Config{} 232 err = yaml.Unmarshal(b, &cfg) 233 if err != nil { 234 return nil, err 235 } 236 return &cfg, nil 237 } 238 239 // UninstallTemplate cleans up installed templates. 240 func UninstallTemplate() { 241 user, err := user.Current() 242 if err != nil { 243 panic(err) 244 } 245 os.RemoveAll(filepath.Join(user.HomeDir, ".trpc-cmdline-assets")) 246 }