...

Source file src/golang.conradwood.net/go-easyops/cmdline/cmdline.go

Documentation: golang.conradwood.net/go-easyops/cmdline

     1  /*
     2  provides a standard configuration mechanism for clients and servers on the commandline
     3  
     4  go-easyops itself can be configured through environment variables and command line parameters. any go-easyops program
     5  includes standard command line parameters. some are described below
     6  
     7  # -h
     8  
     9  prints out build information and command line parameters for the application
    10  
    11  # -X
    12  
    13  prints out build information and command line parameters to the behaviour of go-easyops
    14  
    15  # Environment Variables
    16  
    17  -h and -X also print environment variables and a short help text for each. Application developers are encouraged to use
    18  this package to manage environment variables. a typical example
    19  
    20  	var (
    21  	  mytext = cmdline.ENV("MYTEXT","specifies the text to display")
    22  	)
    23  	func main() {
    24  	  fmt.Println(mytext.Value())
    25  	}
    26  
    27  # Config Files
    28  
    29  a config file (typically /tmp/goeasyops.config) provides optional and initial configuration for go-easyops. This is intented to configure developer machines on-the-fly for access to a different cloud and cluster. For example, based on the current path, a git repository url may be used to configure a specific and matching registry. (config file syntax is in yaml, see goeasyops proto, protobuf "Config")
    30  */
    31  package cmdline
    32  
    33  import (
    34  	"flag"
    35  	"fmt"
    36  	"io/ioutil"
    37  	"os"
    38  	"strings"
    39  	"sync"
    40  	"time"
    41  
    42  	pb "golang.conradwood.net/apis/goeasyops"
    43  	"golang.conradwood.net/go-easyops/appinfo"
    44  	"golang.conradwood.net/go-easyops/common"
    45  	"gopkg.in/yaml.v2"
    46  )
    47  
    48  const (
    49  	CONFIG_FILE      = "/tmp/goeasyops.config"
    50  	REGISTRY_DEFAULT = "localhost:5000"
    51  )
    52  
    53  var (
    54  	debug_rpc_client = flag.Bool("ge_debug_rpc_client", false, "set to true to debug remote invokations")
    55  	debug_rpc_serve  = flag.Bool("ge_debug_rpc_server", false, "debug the grpc server ")
    56  	default_timeout  = flag.Duration("ge_ctx_deadline", time.Duration(10)*time.Second, "the default timeout for contexts. do not change in production")
    57  	reg_env          = ENV("GE_REGISTRY", "default registry address")
    58  	e_ctx            = ENV("GE_CTX", "a serialised context to use when creating new ones")
    59  	config           *pb.Config
    60  	// annoyingly, not all go-easyops flags start with ge_
    61  	internal_flag_names   = []string{"token", "registry", "registry_resolver", "AD_started_by_auto_deployer", "X"}
    62  	debug_auth            = flag.Bool("ge_debug_auth", false, "debug auth stuff")
    63  	debug_sig             = flag.Bool("ge_debug_signature", false, "debug signature stuff")
    64  	mlock                 sync.Mutex
    65  	running_in_datacenter = flag.Bool("AD_started_by_auto_deployer", false, "the autodeployer sets this to true to modify the behaviour to make it suitable for general-availability services in the datacenter")
    66  
    67  	registry          = flag.String("registry", REGISTRY_DEFAULT, "address of the registry server. This is used for registration as well as resolving unless -registry_resolver is set, in which case this is only used for registration")
    68  	registry_resolver = flag.String("registry_resolver", "", "address of the registry server (for lookups)")
    69  	instance_id       = flag.String("ge_instance_id", "", "autodeployers internal instance id. We may use this to get information about ourselves")
    70  	ext_help          = flag.Bool("X", false, "extended help")
    71  	XXdoappinfo       = ImmediatePara("ge_info", "print application build number", doappinfo)
    72  	print_easyops     = false
    73  	manreg            = ""
    74  	stdalone          = flag.Bool("ge_standalone", false, "if true, do not use a registry, just run stuff standlone")
    75  	//	context_with_builder   = flag.Bool("ge_context_with_builder", true, "a new (experimental) context messaging method")
    76  	context_build_version  = flag.Int("ge_context_builder_version", 2, "the version to create by the context builder (0=do not use context builder)")
    77  	overridden_env_context = ""
    78  	enabled_experiments    = flag.String("ge_enable_experiments", "", "a comma delimited set of names of experiments to enable with this context")
    79  	debug_ctx              = flag.Bool("ge_debug_context", false, "if true debug context stuff")
    80  )
    81  
    82  // in the init function we have not yet defined all the flags
    83  // each init() is called in the order of import statements, thus packages imported AFTER this package
    84  // won't have their flags initialized yet
    85  // I have not found a good way of being triggered once flags are parsed, thus we use a timer in the hope that it will work well enough
    86  func init() {
    87  	flag.Usage = PrintUsage
    88  	for _, o := range os.Args {
    89  		if o == "-X" {
    90  			go print_late_usage()
    91  		}
    92  	}
    93  
    94  	// read a potential config file
    95  	err := readConfig(CONFIG_FILE)
    96  	if err != nil {
    97  		os.Exit(10)
    98  	}
    99  }
   100  func readConfig(filename string) error {
   101  	_, err := os.Stat(filename)
   102  	if err != nil {
   103  		return nil // if file does not exist, it's not an error
   104  	}
   105  	b, err := ioutil.ReadFile(filename)
   106  	if err != nil {
   107  		fmt.Printf("[go-easyops] failed to read file %s: %s\n", filename, err)
   108  		return err
   109  	}
   110  	cfg := &pb.Config{}
   111  	err = yaml.UnmarshalStrict(b, cfg)
   112  	if err != nil {
   113  		fmt.Printf("[go-easyops] invalid file %s: %s\n", filename, err)
   114  		return err
   115  	}
   116  	config = cfg
   117  	return nil
   118  }
   119  
   120  // if we have a -X argument we will print extended usage AFTER flags are parsed.
   121  // we know flags areg parsed if ext_help flag (-X) turns true (timeout after 5 secs)
   122  func print_late_usage() {
   123  	fmt.Printf("[go-easyops] Printing extended help after flag.Parse() was called...\n")
   124  	st := time.Now()
   125  	for *ext_help == false {
   126  		if time.Since(st) > time.Duration(5)*time.Second {
   127  			break
   128  		}
   129  	}
   130  	print_easyops = true
   131  	PrintUsage()
   132  	os.Exit(0)
   133  }
   134  
   135  func PrintUsage() {
   136  	fmt.Fprintf(os.Stdout, "(C) Conrad Wood.\n")
   137  	fmt.Fprintf(os.Stdout, "  Go-Easyops version          : %d\n", BUILD_NUMBER)
   138  	fmt.Fprintf(os.Stdout, "  Go-Easyops build timestamp  : %d\n", BUILD_TIMESTAMP)
   139  	fmt.Fprintf(os.Stdout, "  Go-Easyops build time       : %s\n", time.Unix(BUILD_TIMESTAMP, 0))
   140  	fmt.Fprintf(os.Stdout, "  Go-Easyops description      : %s\n", BUILD_DESCRIPTION)
   141  
   142  	fmt.Fprintf(os.Stdout, "  App version                 : %d\n", appinfo.AppInfo().Number)
   143  	fmt.Fprintf(os.Stdout, "  App build timestamp         : %d\n", appinfo.AppInfo().Timestamp)
   144  	fmt.Fprintf(os.Stdout, "  App build time              : %s\n", time.Unix(appinfo.AppInfo().Timestamp, 0))
   145  	fmt.Fprintf(os.Stdout, "  App description             : %s\n", appinfo.AppInfo().Description)
   146  	fmt.Fprintf(os.Stdout, "  App artefactid              : %d\n", appinfo.AppInfo().ArtefactID)
   147  	fmt.Fprintf(os.Stdout, "  App repository              : %d\n", appinfo.AppInfo().RepositoryID)
   148  	fmt.Fprintf(os.Stdout, "  App repository git url      : %s\n", appinfo.AppInfo().GitURL)
   149  	fmt.Fprintf(os.Stdout, "  Source code path            : %s\n", SourceCodePath())
   150  
   151  	PrintDefaults()
   152  }
   153  func PrintDefaults() {
   154  	if print_easyops {
   155  		fmt.Fprintf(os.Stdout, "\nGo-easyops Usage:\n")
   156  	} else {
   157  		fmt.Fprintf(os.Stdout, "\nUsage:\n")
   158  	}
   159  	f := flag.CommandLine
   160  	f.VisitAll(func(fg *flag.Flag) {
   161  		isext := strings.HasPrefix(fg.Name, "ge_")
   162  		if !isext {
   163  			for _, s := range internal_flag_names {
   164  				if fg.Name == s {
   165  					isext = true
   166  					break
   167  				}
   168  			}
   169  		}
   170  		if print_easyops != isext {
   171  			return
   172  		}
   173  		s := fmt.Sprintf("  -%s", fg.Name) // Two spaces before -; see next two comments.
   174  		name, usage := flag.UnquoteUsage(fg)
   175  		if len(name) > 0 {
   176  			s += " " + name
   177  		}
   178  		// Boolean flags of one ASCII letter are so common we
   179  		// treat them specially, putting their usage on the same line.
   180  		if len(s) <= 4 { // space, space, '-', 'x'.
   181  			s += "\t"
   182  		} else {
   183  			// Four spaces before the tab triggers good alignment
   184  			// for both 4- and 8-space tab stops.
   185  			s += "\n    \t"
   186  		}
   187  		s += strings.ReplaceAll(usage, "\n", "\n    \t")
   188  
   189  		s += fmt.Sprintf(" (default %v)", fg.DefValue)
   190  
   191  		fmt.Printf("%s\n", s)
   192  	})
   193  	fmt.Printf(`
   194  Yaml Mapping file: /etc/yacloud/config/service_map.yaml
   195  Defaults override file: /tmp/goeasyops.config
   196  Environment Variables:
   197  `)
   198  
   199  	s := render_env_help()
   200  	fmt.Println(s)
   201  
   202  }
   203  func GetInstanceID() string {
   204  	s := *instance_id
   205  	if s == "" {
   206  		mlock.Lock()
   207  		defer mlock.Unlock()
   208  		if *instance_id != "" {
   209  			return *instance_id
   210  		}
   211  		s = "L-" + RandomString(32)
   212  		*instance_id = s
   213  	}
   214  	return s
   215  }
   216  func GetPid() uint64 {
   217  	p := os.Getpid()
   218  	return uint64(p)
   219  }
   220  
   221  // get registry address as per -registry parameter, or if -registry_resolver is set, use that
   222  func GetClientRegistryAddress() string {
   223  	if manreg != "" {
   224  		return manreg
   225  	}
   226  	if *registry_resolver == "" {
   227  		return GetRegistryAddress()
   228  	}
   229  	res := *registry_resolver
   230  	if !strings.Contains(res, ":") {
   231  		res = fmt.Sprintf("%s:5000", res)
   232  	}
   233  	return res
   234  }
   235  
   236  // programmatically override -registry_resolver flag
   237  func SetClientRegistryAddress(reg string) {
   238  	if !strings.Contains(reg, ":") {
   239  		reg = fmt.Sprintf("%s:5000", reg)
   240  	}
   241  	manreg = reg
   242  	common.NotifyRegistryChangeListeners()
   243  }
   244  
   245  // get registry address as per -registry parameter
   246  func GetRegistryAddress() string {
   247  	res := *registry
   248  	if *registry == REGISTRY_DEFAULT {
   249  		s := reg_env.Value()
   250  		if s != "" {
   251  			res = s
   252  		}
   253  	}
   254  	if *registry == REGISTRY_DEFAULT {
   255  		if config != nil && config.Registry != "" {
   256  			res = config.Registry
   257  		}
   258  	}
   259  	if !strings.Contains(res, ":") {
   260  		res = fmt.Sprintf("%s:5000", res)
   261  	}
   262  	return res
   263  }
   264  
   265  func Datacenter() bool {
   266  	return *running_in_datacenter
   267  }
   268  
   269  // for testing purposes to mock parameter -AD_started_by_auto_deployer
   270  func SetDatacenter(b bool) {
   271  	*running_in_datacenter = b
   272  }
   273  
   274  // if (para != "") { return para }, else return os.GetEnv(envname)
   275  func OptEnvString(para, envname string) string {
   276  	if para != "" {
   277  		return para
   278  	}
   279  	return os.Getenv(envname)
   280  }
   281  func doappinfo() {
   282  	fmt.Printf("%d\n", appinfo.AppInfo().Number)
   283  	os.Exit(0)
   284  }
   285  func IsStandalone() bool {
   286  	return *stdalone
   287  }
   288  func LocalRegistrationDir() string {
   289  	return "/tmp/local_registry"
   290  }
   291  func ContextWithBuilder() bool {
   292  	return true
   293  
   294  }
   295  
   296  // this is for testing purposes to mock the parameter -ge_context_with_builder
   297  func GetContextBuilderVersion() int {
   298  	version := *context_build_version
   299  	if version != 2 {
   300  		panic(fmt.Sprintf("Unsupported context version (%d)", version))
   301  	}
   302  	return version
   303  }
   304  
   305  // this is for testing purposes to mock the parameter -ge_context_with_builder
   306  func SetContextBuilderVersion(version int) {
   307  	if version != 2 {
   308  		panic(fmt.Sprintf("Unsupported context version (%d)", version))
   309  	}
   310  	*context_build_version = version
   311  }
   312  
   313  // get a serialised context from environment variable GE_CTX
   314  func GetEnvContext() string {
   315  	if overridden_env_context != "" {
   316  		s := overridden_env_context
   317  		if len(s) > 10 {
   318  			s = s[:10]
   319  		}
   320  		fmt.Printf("[go-easyops] using overriden env context (%s )\n", s)
   321  		return overridden_env_context
   322  	}
   323  	return e_ctx.Value()
   324  }
   325  func DebugSignature() bool {
   326  	return *debug_sig
   327  }
   328  func DebugAuth() bool {
   329  	return *debug_auth
   330  }
   331  
   332  // this is for testing purposes to mock the environment variable GE_CTX
   333  func SetEnvContext(s string) {
   334  	overridden_env_context = s
   335  }
   336  
   337  // usually returns /opt/yacloud/current
   338  func GetYACloudDir() string {
   339  	dirs := []string{
   340  		"/opt/yacloud/current",
   341  		"/opt/yacloud/",
   342  	}
   343  	for _, res := range dirs {
   344  		dname := res + "/ctools"
   345  		st, err := os.Stat(dname)
   346  		if err != nil {
   347  			continue
   348  		}
   349  		if st.IsDir() {
   350  			return res
   351  		}
   352  	}
   353  	return ""
   354  }
   355  
   356  // default timeout for new contexts
   357  func DefaultTimeout() time.Duration {
   358  	return *default_timeout
   359  }
   360  
   361  func EnabledExperiments() []string {
   362  	exs := *enabled_experiments
   363  	if len(exs) == 0 {
   364  		return nil
   365  	}
   366  	ex := strings.Split(exs, ",")
   367  	var res []string
   368  	for _, e := range ex {
   369  		e = strings.Trim(e, " ")
   370  		res = append(res, e)
   371  	}
   372  	return res
   373  }
   374  
   375  // print context debug stuff
   376  func DebugfContext(format string, args ...interface{}) {
   377  	if !*debug_ctx {
   378  		return
   379  	}
   380  	x := fmt.Sprintf(format, args...)
   381  	fmt.Printf("[go-easyops/debugctx] %s", x)
   382  }
   383  
   384  // print context debug stuff
   385  func DebugfRPC(format string, args ...interface{}) {
   386  	if !*debug_ctx {
   387  		return
   388  	}
   389  	x := fmt.Sprintf(format, args...)
   390  	fmt.Printf("[go-easyops/rpc] %s", x)
   391  }
   392  
   393  func SetDebugContext() {
   394  	*debug_ctx = true
   395  }
   396  func IsDebugRPCClient() bool {
   397  	return *debug_rpc_client
   398  }
   399  func IsDebugRPCServer() bool {
   400  	return *debug_rpc_serve
   401  }
   402  
   403  // is this a flag defined and used by go-easyops?
   404  func IsEasyopsFlag(name string) bool {
   405  	if strings.HasPrefix(name, "ge_") {
   406  		return true
   407  	}
   408  	for _, ifn := range internal_flag_names {
   409  		if ifn == name {
   410  			return true
   411  		}
   412  	}
   413  	return false
   414  }
   415  

View as plain text