package cmd import ( "flag" "fmt" "log" "os" "strings" "github.com/bugout-dev/moonstream/nodes/node_balancer/configs" ) var ( stateCLI StateCLI bugoutClient bugout.BugoutClient ) type flagSlice []string func (i *flagSlice) String() string { return strings.Join(*i, ", ") } func (i *flagSlice) Set(value string) error { *i = append(*i, value) return nil } // Command Line Interface state type StateCLI struct { addAccessCmd *flag.FlagSet deleteAccessCmd *flag.FlagSet serverCmd *flag.FlagSet usersCmd *flag.FlagSet versionCmd *flag.FlagSet // Common flags configPathFlag string helpFlag bool // Add user access flags userIDFlag string accessIDFlag string accessNameFlag string accessDescriptionFlag string blockchainAccessFlag bool extendedMethodsFlag bool // Server flags listeningAddrFlag string listeningPortFlag string enableHealthCheckFlag bool enableDebugFlag bool // Users list flags limitFlag int offsetFlag int } type PingResponse struct { Status string `json:"status"` } type UserAccess struct { UserID string `json:"user_id"` AccessID string `json:"access_id"` Name string `json:"name"` Description string `json:"description"` BlockchainAccess bool `json:"blockchain_access"` ExtendedMethods bool `json:"extended_methods"` dataSource string } func (s *StateCLI) usage() { fmt.Printf(`usage: nodebalancer [-h] {%[1]s,%[2]s,%[3]s,%[4]s,%[5]s} ... Moonstream node balancer CLI optional arguments: -h, --help show this help message and exit subcommands: {%[1]s,%[2]s,%[3]s,%[4]s,%[5]s} `, s.addAccessCmd.Name(), s.deleteAccessCmd.Name(), s.serverCmd.Name(), s.usersCmd.Name(), s.versionCmd.Name()) } func (s *StateCLI) checkRequirements() { if s.helpFlag { switch { case s.addAccessCmd.Parsed(): fmt.Printf("Add new user access token\n\n") s.addAccessCmd.PrintDefaults() os.Exit(0) case s.deleteAccessCmd.Parsed(): fmt.Printf("Delete user access token\n\n") s.deleteAccessCmd.PrintDefaults() os.Exit(0) case s.serverCmd.Parsed(): fmt.Printf("Start nodebalancer server\n\n") s.serverCmd.PrintDefaults() os.Exit(0) case s.usersCmd.Parsed(): fmt.Printf("List user access tokens\n\n") s.usersCmd.PrintDefaults() os.Exit(0) case s.versionCmd.Parsed(): fmt.Printf("Show version\n\n") s.versionCmd.PrintDefaults() os.Exit(0) default: s.usage() os.Exit(0) } } switch { case s.addAccessCmd.Parsed(): if s.userIDFlag == "" { fmt.Printf("User ID should be specified\n\n") s.addAccessCmd.PrintDefaults() os.Exit(1) } if s.accessIDFlag == "" { s.accessIDFlag = uuid.New().String() } if s.accessNameFlag == "" { fmt.Printf("Access name should be specified\n\n") s.addAccessCmd.PrintDefaults() os.Exit(1) } case s.deleteAccessCmd.Parsed(): if s.userIDFlag == "" && s.accessIDFlag == "" { fmt.Printf("User or access ID flag should be specified\n\n") s.deleteAccessCmd.PrintDefaults() os.Exit(1) } case s.usersCmd.Parsed(): if s.offsetFlag < 0 || s.limitFlag < 0 { fmt.Printf("Offset and limit flags should be greater then zero\n\n") s.usersCmd.PrintDefaults() os.Exit(1) } if s.configPathFlag == "" { homeDir, err := os.UserHomeDir() if err != nil { log.Fatalf("Unable to find user home directory, %v", err) } configDirPath := fmt.Sprintf("%s/.nodebalancer", homeDir) configPath := fmt.Sprintf("%s/config.txt", configDirPath) err = os.MkdirAll(configDirPath, os.ModePerm) if err != nil { log.Fatalf("Unable to create directory, %v", err) } _, err = os.Stat(configPath) if err != nil { tempConfigB := []byte("ethereum,http://127.0.0.1,8545") err = os.WriteFile(configPath, tempConfigB, 0644) if err != nil { log.Fatalf("Unable to write config, %v", err) } } s.configPathFlag = configPath } } func (s *StateCLI) populateCLI() { // Subcommands setup s.addAccessCmd = flag.NewFlagSet("add-access", flag.ExitOnError) s.deleteAccessCmd = flag.NewFlagSet("delete-access", flag.ExitOnError) s.serverCmd = flag.NewFlagSet("server", flag.ExitOnError) s.usersCmd = flag.NewFlagSet("users", flag.ExitOnError) s.versionCmd = flag.NewFlagSet("version", flag.ExitOnError) // Common flag pointers for _, fs := range []*flag.FlagSet{s.addAccessCmd, s.deleteAccessCmd, s.serverCmd, s.usersCmd, s.versionCmd} { fs.BoolVar(&s.helpFlag, "help", false, "Show help message") fs.StringVar(&s.configPathFlag, "config", "", "Path to configuration file (default: ~/.nodebalancer/config.txt)") } // Add, delete and list user access subcommand flag pointers for _, fs := range []*flag.FlagSet{s.addAccessCmd, s.deleteAccessCmd, s.usersCmd} { fs.StringVar(&s.userIDFlag, "user-id", "", "Bugout user ID") fs.StringVar(&s.accessIDFlag, "access-id", "", "UUID for access identification") } // Add user access subcommand flag pointers s.addAccessCmd.StringVar(&s.accessNameFlag, "name", "", "Name of access") s.addAccessCmd.StringVar(&s.accessDescriptionFlag, "description", "", "Description of access") s.addAccessCmd.BoolVar(&s.blockchainAccessFlag, "blockchain-access", false, "Provide if allow to access blockchain nodes") s.addAccessCmd.BoolVar(&s.extendedMethodsFlag, "extended-methods", false, "Provide to be able to execute not whitelisted methods") // Server subcommand flag pointers s.serverCmd.StringVar(&s.listeningAddrFlag, "host", "127.0.0.1", "Server listening address") s.serverCmd.StringVar(&s.listeningPortFlag, "port", "8544", "Server listening port") s.serverCmd.BoolVar(&s.enableHealthCheckFlag, "healthcheck", false, "To enable healthcheck ser healthcheck flag") s.serverCmd.BoolVar(&s.enableDebugFlag, "debug", false, "To enable debug mode with extended log set debug flag") // Users list subcommand flag pointers s.usersCmd.IntVar(&s.limitFlag, "limit", 10, "Output result limit") s.usersCmd.IntVar(&s.offsetFlag, "offset", 0, "Result output offset") } func CLI() { stateCLI.populateCLI() if len(os.Args) < 2 { stateCLI.usage() os.Exit(1) } // Parse subcommands and appropriate FlagSet switch os.Args[1] { case "add-access": stateCLI.addAccessCmd.Parse(os.Args[2:]) stateCLI.checkRequirements() proposedUserAccess := UserAccess{ UserID: stateCLI.userIDFlag, AccessID: stateCLI.accessIDFlag, Name: stateCLI.accessNameFlag, Description: stateCLI.accessDescriptionFlag, BlockchainAccess: stateCLI.blockchainAccessFlag, ExtendedMethods: stateCLI.extendedMethodsFlag, } _, err := bugoutClient.Brood.FindUser( configs.NB_CONTROLLER_TOKEN, map[string]string{ "user_id": proposedUserAccess.UserID, "application_id": configs.NB_APPLICATION_ID, }, ) if err != nil { fmt.Printf("User does not exists %v\n", err) os.Exit(1) } resource, err := bugoutClient.Brood.CreateResource(configs.NB_CONTROLLER_TOKEN, configs.NB_APPLICATION_ID, proposedUserAccess) if err != nil { fmt.Printf("Unable to create user access %v\n", err) os.Exit(1) } resource_data, err := json.Marshal(resource.ResourceData) if err != nil { fmt.Printf("Unable to encode resource %s data interface to json %v", resource.Id, err) os.Exit(1) } fmt.Println(string(resource_data)) case "delete-access": stateCLI.deleteAccessCmd.Parse(os.Args[2:]) stateCLI.checkRequirements() queryParameters := make(map[string]string) if stateCLI.userIDFlag != "" { queryParameters["user_id"] = stateCLI.userIDFlag } if stateCLI.accessIDFlag != "" { queryParameters["access_id"] = stateCLI.accessIDFlag } resources, err := bugoutClient.Brood.GetResources( configs.NB_CONTROLLER_TOKEN, configs.NB_APPLICATION_ID, queryParameters, ) if err != nil { fmt.Printf("Unable to get Bugout resources %v\n", err) os.Exit(1) } var userAccesses []UserAccess for _, resource := range resources.Resources { deletedResource, err := bugoutClient.Brood.DeleteResource(configs.NB_CONTROLLER_TOKEN, resource.Id) if err != nil { fmt.Printf("Unable to delete resource %s %v\n", resource.Id, err) continue } resource_data, err := json.Marshal(deletedResource.ResourceData) if err != nil { fmt.Printf("Unable to encode resource %s data interface to json %v", resource.Id, err) continue } var userAccess UserAccess err = json.Unmarshal(resource_data, &userAccess) if err != nil { fmt.Printf("Unable to decode resource %s data json to structure %v", resource.Id, err) continue } userAccesses = append(userAccesses, userAccess) } userAccessesJson, err := json.Marshal(userAccesses) if err != nil { fmt.Printf("Unable to marshal user access struct %v\n", err) os.Exit(1) } fmt.Println(string(userAccessesJson)) case "server": stateCLI.serverCmd.Parse(os.Args[2:]) stateCLI.checkRequirements() Server() case "users": stateCLI.usersCmd.Parse(os.Args[2:]) stateCLI.checkRequirements() var queryParameters map[string]string if stateCLI.userIDFlag != "" { queryParameters["user_id"] = stateCLI.userIDFlag } if stateCLI.accessIDFlag != "" { queryParameters["access_id"] = stateCLI.accessIDFlag } resources, err := bugoutClient.Brood.GetResources( configs.NB_CONTROLLER_TOKEN, configs.NB_APPLICATION_ID, queryParameters, ) if err != nil { fmt.Printf("Unable to get Bugout resources %v\n", err) os.Exit(1) } var userAccesses []UserAccess offset := stateCLI.offsetFlag if stateCLI.offsetFlag > len(resources.Resources) { offset = len(resources.Resources) } limit := stateCLI.offsetFlag + stateCLI.limitFlag if limit > len(resources.Resources) { limit = len(resources.Resources[offset:]) + offset } for _, resource := range resources.Resources[offset:limit] { resource_data, err := json.Marshal(resource.ResourceData) if err != nil { fmt.Printf("Unable to encode resource %s data interface to json %v", resource.Id, err) continue } var userAccess UserAccess err = json.Unmarshal(resource_data, &userAccess) if err != nil { fmt.Printf("Unable to decode resource %s data json to structure %v", resource.Id, err) continue } userAccesses = append(userAccesses, userAccess) } userAccessesJson, err := json.Marshal(userAccesses) if err != nil { fmt.Printf("Unable to marshal user accesses struct %v\n", err) os.Exit(1) } fmt.Println(string(userAccessesJson)) case "version": stateCLI.versionCmd.Parse(os.Args[2:]) stateCLI.checkRequirements() fmt.Printf("v%s\n", configs.NB_VERSION) default: stateCLI.usage() os.Exit(1) } } func init() { configs.VerifyEnvironments() // Init bugout client bc, err := bugout.ClientFromEnv() if err != nil { fmt.Printf("Unable to initialize bugout client %v", err) os.Exit(1) } bugoutClient = bc }