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  }