diff --git a/nodebalancer/cmd/nodebalancer/cli.go b/nodebalancer/cmd/nodebalancer/cli.go index f33699e5..49769c53 100644 --- a/nodebalancer/cmd/nodebalancer/cli.go +++ b/nodebalancer/cmd/nodebalancer/cli.go @@ -2,519 +2,275 @@ package main import ( "encoding/json" - "flag" "fmt" - "log" - "os" "time" - bugout "github.com/bugout-dev/bugout-go/pkg" - "github.com/google/uuid" + "github.com/urfave/cli/v2" ) -var ( - // Storing CLI definitions at server startup - stateCLI StateCLI +var CommonCommands = []*cli.Command{ + { + Name: "access", + Usage: "Operations with access IDs as Brood resource", + Flags: []cli.Flag{}, + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "Add new user's access ID", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "access-token", + Aliases: []string{"t"}, + Usage: "Authorized user access token with granted privileges to create resources in Moonstream Bugout application and sharing read permissions to nodebalancer application user", + Required: true, + }, + &cli.StringFlag{ + Name: "access-id", + Aliases: []string{"a"}, + Usage: "UUID for access identification", + Required: true, + }, + &cli.StringFlag{ + Name: "user-id", + Aliases: []string{"u"}, + Usage: "Bugout user ID", + Required: true, + }, + &cli.StringFlag{ + Name: "name", + Aliases: []string{"n"}, + Usage: "Name of the user or application to work with nodebalancer", + Required: true, + }, + &cli.StringFlag{ + Name: "description", + Aliases: []string{"d"}, + }, + &cli.BoolFlag{ + Name: "blockchain-access", + Aliases: []string{"b"}, + Usage: "Specify this flag to grant direct access to blockchain nodes", + }, + &cli.BoolFlag{ + Name: "extended-methods", + Aliases: []string{"e"}, + Usage: "Specify this flag to grant execution availability to not whitelisted methods", + }, + &cli.UintFlag{ + Name: "period-duration", + Aliases: []string{"p"}, + Usage: "Access period duration in seconds", + Value: 2592000, + }, + &cli.UintFlag{ + Name: "max-calls-per-period", + Aliases: []string{"m"}, + Usage: "Max available calls to node during the period", + Value: 10000, + }, + }, + Before: func(c *cli.Context) error { + periodDurationFlag := c.Uint("period-duration") - bugoutClient bugout.BugoutClient + if periodDurationFlag < 3600 { + return fmt.Errorf("time for --period-duration should be greater then 1 hour") + } - DEFAULT_ACCESS_NAME = "" - DEFAULT_ACCESS_DESCRIPTION = "" - DEFAULT_BLOCKCHAIN_ACCESS = true - DEFAULT_EXTENDED_METHODS = true - DEFAULT_PERIOD_DURATION = int64(86400) // 1 day - DEFAULT_MAX_CALLS_PER_PERIOD = int64(10000) -) + return nil + }, + Action: func(c *cli.Context) error { + var clientErr error + bugoutClient, clientErr = CreateBugoutClient() + if clientErr != nil { + return fmt.Errorf("an error occurred during Bugout client creation, err: %v", clientErr) + } -// Command Line Interface state -type StateCLI struct { - addAccessCmd *flag.FlagSet - updateAccessCmd *flag.FlagSet - generateConfigCmd *flag.FlagSet - deleteAccessCmd *flag.FlagSet - serverCmd *flag.FlagSet - usersCmd *flag.FlagSet - versionCmd *flag.FlagSet + newAccess, newErr := AddNewAccess(c.String("access-id"), c.String("user-id"), c.String("name"), c.String("description"), c.Bool("blockchain-access"), c.Bool("extended-methods"), c.Uint("period-duration"), c.Uint("max-calls-per-period"), c.String("access-token")) + if newErr != nil { + return fmt.Errorf("failed to add new access, err: %v", newErr) + } - // Common flags - configPathFlag string - helpFlag bool + _, shareErr := ShareAccess(newAccess.ResourceID, NB_CONTROLLER_USER_ID, "user", DEFAULT_AUTOGENERATED_USER_PERMISSIONS, c.String("access-token")) + if shareErr != nil { + return fmt.Errorf("failed to share access to resource ID %s with nodebalancer application user, err: %v", newAccess.ResourceID, shareErr) + } - // Add/update user access flags - userIDFlag string - accessIDFlag string - accessNameFlag string - accessDescriptionFlag string + newAccessJson, err := json.Marshal(newAccess) + if err != nil { + return fmt.Errorf("unable to encode resource %s data interface to json, err: %v", newAccess.ResourceID, err) + } + fmt.Println(string(newAccessJson)) - blockchainAccessFlag bool - extendedMethodsFlag bool - - PeriodDurationFlag int64 - MaxCallsPerPeriodFlag int64 - - // Update user access flags - PeriodStartTsFlag int64 - CallsPerPeriodFlag int64 - - // Server flags - listeningAddrFlag string - listeningPortFlag string - enableHealthCheckFlag bool - enableDebugFlag bool - - // Users list flags - limitFlag int - offsetFlag int -} - -func (s *StateCLI) usage() { - fmt.Printf(`usage: nodebalancer [-h] {%[1]s,%[2]s,%[3]s,%[4]s,%[5]s,%[6]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,%[6]s,%[7]s} -`, s.addAccessCmd.Name(), s.updateAccessCmd.Name(), s.generateConfigCmd.Name(), s.deleteAccessCmd.Name(), s.serverCmd.Name(), s.usersCmd.Name(), s.versionCmd.Name()) -} - -// Check if required flags are set -func (s *StateCLI) checkRequirements() { - if s.helpFlag { - switch { - case s.addAccessCmd.Parsed(): - fmt.Printf("Add new user access resource\n\n") - s.addAccessCmd.PrintDefaults() - os.Exit(0) - case s.updateAccessCmd.Parsed(): - fmt.Printf("Update user access resource\n\n") - s.updateAccessCmd.PrintDefaults() - os.Exit(0) - case s.generateConfigCmd.Parsed(): - fmt.Printf("Generate new configuration\n\n") - s.generateConfigCmd.PrintDefaults() - os.Exit(0) - case s.deleteAccessCmd.Parsed(): - fmt.Printf("Delete user access resource\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.updateAccessCmd.Parsed(): - if s.userIDFlag == "" && s.accessIDFlag == "" { - fmt.Printf("User ID or access ID should be specified\n\n") - s.updateAccessCmd.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) - } - } - - // Load configuration - config, err := GetConfigPath(s.configPathFlag) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - if !config.ConfigExists { - if err := GenerateDefaultConfig(config); err != nil { - fmt.Println(err) - os.Exit(1) - } - } else { - log.Printf("Loaded configuration from %s", config.ConfigPath) - } - s.configPathFlag = config.ConfigPath -} - -func (s *StateCLI) populateCLI() { - // Subcommands setup - s.addAccessCmd = flag.NewFlagSet("add-access", flag.ExitOnError) - s.updateAccessCmd = flag.NewFlagSet("update-access", flag.ExitOnError) - s.generateConfigCmd = flag.NewFlagSet("generate-config", 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.updateAccessCmd, s.generateConfigCmd, 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.json)") - } - - // Add, delete and list user access subcommand flag pointers - for _, fs := range []*flag.FlagSet{s.addAccessCmd, s.updateAccessCmd, s.deleteAccessCmd, s.usersCmd} { - fs.StringVar(&s.userIDFlag, "user-id", "", "Bugout user ID") - fs.StringVar(&s.accessIDFlag, "access-id", "", "UUID for access identification") - } - - // Add/update user access subcommand flag pointers - for _, fs := range []*flag.FlagSet{s.addAccessCmd, s.updateAccessCmd} { - fs.StringVar(&s.accessNameFlag, "name", DEFAULT_ACCESS_NAME, fmt.Sprintf("Name of access (default: %s)", DEFAULT_ACCESS_NAME)) - fs.StringVar(&s.accessDescriptionFlag, "description", DEFAULT_ACCESS_DESCRIPTION, fmt.Sprintf("Description of access (default: %s)", DEFAULT_ACCESS_DESCRIPTION)) - fs.BoolVar(&s.blockchainAccessFlag, "blockchain-access", DEFAULT_BLOCKCHAIN_ACCESS, fmt.Sprintf("Specify this flag to grant direct access to blockchain nodes (default: %t)", DEFAULT_BLOCKCHAIN_ACCESS)) - fs.BoolVar(&s.extendedMethodsFlag, "extended-methods", DEFAULT_EXTENDED_METHODS, fmt.Sprintf("Specify this flag to grant execution availability to not whitelisted methods (default: %t)", DEFAULT_EXTENDED_METHODS)) - fs.Int64Var(&s.PeriodDurationFlag, "period-duration", DEFAULT_PERIOD_DURATION, fmt.Sprintf("Access period duration in seconds (default: %d)", DEFAULT_PERIOD_DURATION)) - fs.Int64Var(&s.MaxCallsPerPeriodFlag, "max-calls-per-period", DEFAULT_MAX_CALLS_PER_PERIOD, fmt.Sprintf("Max available calls to node during the period (default: %d)", DEFAULT_MAX_CALLS_PER_PERIOD)) - } - - s.updateAccessCmd.Int64Var(&s.PeriodStartTsFlag, "period-start-ts", 0, "When period starts in unix timestamp format (default: now)") - s.updateAccessCmd.Int64Var(&s.CallsPerPeriodFlag, "calls-per-period", 0, "Current number of calls to node during the period (default: 0)") - - // 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 set 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) - } - - // Init bugout client - bc, err := CreateBugoutClient() - if err != nil { - log.Printf("An error occurred during Bugout client creation: %v", err) - os.Exit(1) - } - bugoutClient = bc - - // Parse subcommands and appropriate FlagSet - switch os.Args[1] { - case "generate-config": - stateCLI.generateConfigCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() - - case "add-access": - stateCLI.addAccessCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() - - proposedClientResourceData := ClientResourceData{ - UserID: stateCLI.userIDFlag, - AccessID: stateCLI.accessIDFlag, - Name: stateCLI.accessNameFlag, - Description: stateCLI.accessDescriptionFlag, - BlockchainAccess: stateCLI.blockchainAccessFlag, - ExtendedMethods: stateCLI.extendedMethodsFlag, - - PeriodDuration: stateCLI.PeriodDurationFlag, - PeriodStartTs: time.Now().Unix(), - MaxCallsPerPeriod: stateCLI.MaxCallsPerPeriodFlag, - CallsPerPeriod: 0, - - Type: BUGOUT_RESOURCE_TYPE_NODEBALANCER_ACCESS, - } - _, err := bugoutClient.Brood.FindUser( - NB_CONTROLLER_TOKEN, - map[string]string{ - "user_id": proposedClientResourceData.UserID, - "application_id": MOONSTREAM_APPLICATION_ID, + return nil + }, }, - ) - if err != nil { - fmt.Printf("User does not exists, err: %v\n", err) - os.Exit(1) - } - resource, err := bugoutClient.Brood.CreateResource(NB_CONTROLLER_TOKEN, MOONSTREAM_APPLICATION_ID, proposedClientResourceData) - if err != nil { - fmt.Printf("Unable to create user access, err: %v\n", err) - os.Exit(1) - } - resourceData, err := json.Marshal(resource.ResourceData) - if err != nil { - fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) - os.Exit(1) - } - var newUserAccess ClientAccess - err = json.Unmarshal(resourceData, &newUserAccess) - if err != nil { - fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) - os.Exit(1) - } - newUserAccess.ResourceID = resource.Id - userAccessJson, err := json.Marshal(newUserAccess) - if err != nil { - fmt.Printf("Unable to encode resource %s data interface to json, err: %v", resource.Id, err) - os.Exit(1) - } - fmt.Println(string(userAccessJson)) + { + Name: "update", + Usage: "Update user's access ID", + Flags: []cli.Flag{}, + Before: func(c *cli.Context) error { return nil }, + Action: func(c *cli.Context) error { return nil }, + }, + { + Name: "delete", + Usage: "Delete user's access ID", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "access-token", + Aliases: []string{"t"}, + Usage: "Authorized user access token with granted privileges to delete resources in Moonstream Bugout application", + Required: true, + }, + &cli.StringFlag{ + Name: "access-id", + Aliases: []string{"a"}, + Usage: "UUID for access identification", + }, + &cli.StringFlag{ + Name: "user-id", + Aliases: []string{"u"}, + Usage: "Filter by user_id", + }, + }, + Before: func(c *cli.Context) error { + accessIdFlag := c.String("access-id") + userIdFlag := c.String("user-id") - case "update-access": - stateCLI.updateAccessCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() + if accessIdFlag == "" && userIdFlag == "" { + return fmt.Errorf("at least one of --access-id or --user-id should be set") + } - 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( - NB_CONTROLLER_TOKEN, - MOONSTREAM_APPLICATION_ID, - queryParameters, - ) - if err != nil { - fmt.Printf("Unable to get Bugout resources, err: %v\n", err) - os.Exit(1) - } + return nil + }, + Action: func(c *cli.Context) error { + var clientErr error + bugoutClient, clientErr = CreateBugoutClient() + if clientErr != nil { + return fmt.Errorf("an error occurred during Bugout client creation: %v", clientErr) + } - resourcesLen := len(resources.Resources) - if resourcesLen == 0 { - fmt.Printf("There are no access resource with provided user-id %s or access-id %s\n", stateCLI.userIDFlag, stateCLI.accessIDFlag) - os.Exit(1) - } - if resourcesLen > 1 { - fmt.Printf("There are several %d access resources with provided user-id %s or access-id %s\n", resourcesLen, stateCLI.userIDFlag, stateCLI.accessIDFlag) - os.Exit(1) - } + resources, getResErr := GetAccesses(c.String("access-id"), c.String("user-id"), c.String("access-token")) + if getResErr != nil { + return fmt.Errorf("unable to get Bugout resources, err: %v", getResErr) + } - resource := resources.Resources[0] - resourceData, err := json.Marshal(resource.ResourceData) - if err != nil { - fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) - os.Exit(1) - } + if len(resources.Resources) == 0 { + fmt.Println("[]") + return nil + } - var currentClientAccess ClientAccess - currentClientAccess.ResourceID = resource.Id - err = json.Unmarshal(resourceData, ¤tClientAccess.ClientResourceData) - if err != nil { - fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) - os.Exit(1) - } + var clientAccesses []ClientAccess + for _, resource := range resources.Resources { + clientAccess, parseErr := ParseResourceDataToClientAccess(resource) + if parseErr != nil { + fmt.Printf("Unable to parse resource data, err: %v", parseErr) + continue + } - // TODO(kompotkot): Since we are using bool flags I moved with ugly solution. - // Let's find better one when have free time or will re-write flag Set. - update := make(map[string]interface{}) - if stateCLI.accessNameFlag != currentClientAccess.ClientResourceData.Name && stateCLI.accessNameFlag != DEFAULT_ACCESS_NAME { - update["name"] = stateCLI.accessNameFlag - } - if stateCLI.accessDescriptionFlag != currentClientAccess.ClientResourceData.Description && stateCLI.accessDescriptionFlag != DEFAULT_ACCESS_DESCRIPTION { - update["description"] = stateCLI.accessDescriptionFlag - } - if stateCLI.blockchainAccessFlag != currentClientAccess.ClientResourceData.BlockchainAccess && stateCLI.blockchainAccessFlag != DEFAULT_BLOCKCHAIN_ACCESS { - update["blockchain_access"] = stateCLI.blockchainAccessFlag - } - if stateCLI.extendedMethodsFlag != currentClientAccess.ClientResourceData.ExtendedMethods && stateCLI.extendedMethodsFlag != DEFAULT_EXTENDED_METHODS { - update["extended_methods"] = stateCLI.extendedMethodsFlag - } - if stateCLI.PeriodDurationFlag != currentClientAccess.ClientResourceData.PeriodDuration && stateCLI.PeriodDurationFlag != DEFAULT_PERIOD_DURATION { - update["period_duration"] = stateCLI.PeriodDurationFlag - } - if stateCLI.MaxCallsPerPeriodFlag != currentClientAccess.ClientResourceData.MaxCallsPerPeriod && stateCLI.MaxCallsPerPeriodFlag != DEFAULT_MAX_CALLS_PER_PERIOD { - update["max_calls_per_period"] = stateCLI.MaxCallsPerPeriodFlag - } - if stateCLI.PeriodStartTsFlag != currentClientAccess.ClientResourceData.PeriodStartTs && stateCLI.PeriodStartTsFlag != 0 { - update["period_start_ts"] = stateCLI.PeriodStartTsFlag - } - if stateCLI.CallsPerPeriodFlag != currentClientAccess.ClientResourceData.CallsPerPeriod && stateCLI.CallsPerPeriodFlag != 0 { - update["calls_per_period"] = stateCLI.CallsPerPeriodFlag - } + clientAccesses = append(clientAccesses, *clientAccess) + } - updatedResource, err := bugoutClient.Brood.UpdateResource( - NB_CONTROLLER_TOKEN, - resource.Id, - update, - []string{}, - ) - if err != nil { - fmt.Printf("Unable to update Bugout resource, err: %v\n", err) - os.Exit(1) - } + for _, access := range clientAccesses { + fmt.Printf("Deleting resource ID %s with name %s in 3 seconds..\n", access.ResourceID, access.ClientResourceData.Name) + time.Sleep(3 * time.Second) - updatedResourceData, err := json.Marshal(updatedResource.ResourceData) - if err != nil { - fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) - os.Exit(1) - } - var updatedUserAccess ClientAccess - err = json.Unmarshal(updatedResourceData, &updatedUserAccess) - if err != nil { - fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) - os.Exit(1) - } - updatedUserAccess.ResourceID = updatedResource.Id - userAccessJson, err := json.Marshal(updatedUserAccess) - if err != nil { - fmt.Printf("Unable to marshal user access struct, err: %v\n", err) - os.Exit(1) - } - fmt.Println(string(userAccessJson)) + _, delErr := DeleteAccess(access.ResourceID, c.String("access-token")) + if delErr != nil { + fmt.Printf("Failed to delete resource with ID %s err: %v\n", access.ResourceID, delErr) + continue + } + } - case "delete-access": - stateCLI.deleteAccessCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() + return nil + }, + }, + { + Name: "list", + Usage: "List all user access IDs", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "access-token", + Aliases: []string{"t"}, + Usage: "Authorized user access token with granted privileges to get resources in Moonstream Bugout application", + Required: true, + }, + &cli.StringFlag{ + Name: "access-id", + Aliases: []string{"a"}, + Usage: "Filter by access_id", + }, + &cli.StringFlag{ + Name: "user-id", + Aliases: []string{"u"}, + Usage: "Filter by user_id", + }, + }, + Action: func(c *cli.Context) error { + var clientErr error + bugoutClient, clientErr = CreateBugoutClient() + if clientErr != nil { + return fmt.Errorf("an error occurred during Bugout client creation: %v", clientErr) + } - 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( - NB_CONTROLLER_TOKEN, - MOONSTREAM_APPLICATION_ID, - queryParameters, - ) - if err != nil { - fmt.Printf("Unable to get Bugout resources, err: %v\n", err) - os.Exit(1) - } + resources, getResErr := GetAccesses(c.String("access-id"), c.String("user-id"), c.String("access-token")) + if getResErr != nil { + return fmt.Errorf("unable to get Bugout resources, err: %v", getResErr) + } - var userAccesses []ClientAccess - for _, resource := range resources.Resources { - deletedResource, err := bugoutClient.Brood.DeleteResource(NB_CONTROLLER_TOKEN, resource.Id) - if err != nil { - fmt.Printf("Unable to delete resource %s, err: %v\n", resource.Id, err) - continue - } - deletedResourceData, err := json.Marshal(deletedResource.ResourceData) - if err != nil { - fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) - continue - } - var deletedUserAccess ClientAccess - err = json.Unmarshal(deletedResourceData, &deletedUserAccess) - if err != nil { - fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) - continue - } - deletedUserAccess.ResourceID = deletedResource.Id - userAccesses = append(userAccesses, deletedUserAccess) - } + var clientAccesses []ClientAccess + for _, resource := range resources.Resources { + clientAccess, parseErr := ParseResourceDataToClientAccess(resource) + if parseErr != nil { + fmt.Printf("Unable to parse resource data, err: %v", parseErr) + continue + } - userAccessesJson, err := json.Marshal(userAccesses) - if err != nil { - fmt.Printf("Unable to marshal user access struct, err: %v\n", err) - os.Exit(1) - } - fmt.Println(string(userAccessesJson)) + clientAccesses = append(clientAccesses, *clientAccess) + } - case "server": - stateCLI.serverCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() + userAccessesJson, marErr := json.Marshal(clientAccesses) + if marErr != nil { + return fmt.Errorf("unable to marshal user accesses struct, err: %v", marErr) + } + fmt.Println(string(userAccessesJson)) - CheckEnvVarSet() + return nil + }, + }, + }, + }, + { + Name: "configure", + Usage: "Generate nodebalancer configuration", + Action: func(cCtx *cli.Context) error { - Server() + return nil + }, + }, + { + Name: "server", + Usage: "Start nodebalancer server", + Action: func(cCtx *cli.Context) error { - case "users": - stateCLI.usersCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() + return nil + }, + }, + { + Name: "version", + Usage: "Shows nodebalancer package version", + Action: func(cCtx *cli.Context) error { + fmt.Println(NB_VERSION) + return nil + }, + }, +} - 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( - NB_CONTROLLER_TOKEN, - MOONSTREAM_APPLICATION_ID, - queryParameters, - ) - if err != nil { - fmt.Printf("Unable to get Bugout resources, err: %v\n", err) - os.Exit(1) - } - - var clientAccesses []ClientAccess - - 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] { - resourceData, err := json.Marshal(resource.ResourceData) - if err != nil { - fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) - continue - } - var clientAccess ClientAccess - clientAccess.ResourceID = resource.Id - err = json.Unmarshal(resourceData, &clientAccess.ClientResourceData) - if err != nil { - fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) - continue - } - clientAccesses = append(clientAccesses, clientAccess) - } - userAccessesJson, err := json.Marshal(clientAccesses) - if err != nil { - fmt.Printf("Unable to marshal user accesses struct, err: %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", NB_VERSION) - - default: - stateCLI.usage() - os.Exit(1) +func NodebalancerAppCli() *cli.App { + return &cli.App{ + Name: "nodebalancer", + Version: fmt.Sprintf("v%s", NB_VERSION), + Usage: "Web3 node balancer", + EnableBashCompletion: true, + Commands: CommonCommands, } } diff --git a/nodebalancer/cmd/nodebalancer/cli_OLD.go b/nodebalancer/cmd/nodebalancer/cli_OLD.go new file mode 100644 index 00000000..04c547c0 --- /dev/null +++ b/nodebalancer/cmd/nodebalancer/cli_OLD.go @@ -0,0 +1,517 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "os" + "time" + + "github.com/google/uuid" +) + +var ( + // Storing CLI definitions at server startup + stateCLI StateCLI + + DEFAULT_ACCESS_NAME = "" + DEFAULT_ACCESS_DESCRIPTION = "" + DEFAULT_BLOCKCHAIN_ACCESS = true + DEFAULT_EXTENDED_METHODS = true + DEFAULT_PERIOD_DURATION = int64(86400) // 1 day + DEFAULT_MAX_CALLS_PER_PERIOD = int64(10000) +) + +// Command Line Interface state +type StateCLI struct { + addAccessCmd *flag.FlagSet + updateAccessCmd *flag.FlagSet + generateConfigCmd *flag.FlagSet + deleteAccessCmd *flag.FlagSet + serverCmd *flag.FlagSet + usersCmd *flag.FlagSet + versionCmd *flag.FlagSet + + // Common flags + configPathFlag string + helpFlag bool + + // Add/update user access flags + userIDFlag string + accessIDFlag string + accessNameFlag string + accessDescriptionFlag string + + blockchainAccessFlag bool + extendedMethodsFlag bool + + PeriodDurationFlag int64 + MaxCallsPerPeriodFlag int64 + + // Update user access flags + PeriodStartTsFlag int64 + CallsPerPeriodFlag int64 + + // Server flags + listeningAddrFlag string + listeningPortFlag string + enableHealthCheckFlag bool + enableDebugFlag bool + + // Users list flags + limitFlag int + offsetFlag int +} + +func (s *StateCLI) usage() { + fmt.Printf(`usage: nodebalancer [-h] {%[1]s,%[2]s,%[3]s,%[4]s,%[5]s,%[6]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,%[6]s,%[7]s} +`, s.addAccessCmd.Name(), s.updateAccessCmd.Name(), s.generateConfigCmd.Name(), s.deleteAccessCmd.Name(), s.serverCmd.Name(), s.usersCmd.Name(), s.versionCmd.Name()) +} + +// Check if required flags are set +func (s *StateCLI) checkRequirements() { + if s.helpFlag { + switch { + case s.addAccessCmd.Parsed(): + fmt.Printf("Add new user access resource\n\n") + s.addAccessCmd.PrintDefaults() + os.Exit(0) + case s.updateAccessCmd.Parsed(): + fmt.Printf("Update user access resource\n\n") + s.updateAccessCmd.PrintDefaults() + os.Exit(0) + case s.generateConfigCmd.Parsed(): + fmt.Printf("Generate new configuration\n\n") + s.generateConfigCmd.PrintDefaults() + os.Exit(0) + case s.deleteAccessCmd.Parsed(): + fmt.Printf("Delete user access resource\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.updateAccessCmd.Parsed(): + if s.userIDFlag == "" && s.accessIDFlag == "" { + fmt.Printf("User ID or access ID should be specified\n\n") + s.updateAccessCmd.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) + } + } + + // Load configuration + config, err := GetConfigPath(s.configPathFlag) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if !config.ConfigExists { + if err := GenerateDefaultConfig(config); err != nil { + fmt.Println(err) + os.Exit(1) + } + } else { + log.Printf("Loaded configuration from %s", config.ConfigPath) + } + s.configPathFlag = config.ConfigPath +} + +func (s *StateCLI) populateCLI() { + // Subcommands setup + s.addAccessCmd = flag.NewFlagSet("add-access", flag.ExitOnError) + s.updateAccessCmd = flag.NewFlagSet("update-access", flag.ExitOnError) + s.generateConfigCmd = flag.NewFlagSet("generate-config", 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.updateAccessCmd, s.generateConfigCmd, 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.json)") + } + + // Add, delete and list user access subcommand flag pointers + for _, fs := range []*flag.FlagSet{s.addAccessCmd, s.updateAccessCmd, s.deleteAccessCmd, s.usersCmd} { + fs.StringVar(&s.userIDFlag, "user-id", "", "Bugout user ID") + fs.StringVar(&s.accessIDFlag, "access-id", "", "UUID for access identification") + } + + // Add/update user access subcommand flag pointers + for _, fs := range []*flag.FlagSet{s.addAccessCmd, s.updateAccessCmd} { + fs.StringVar(&s.accessNameFlag, "name", DEFAULT_ACCESS_NAME, fmt.Sprintf("Name of access (default: %s)", DEFAULT_ACCESS_NAME)) + fs.StringVar(&s.accessDescriptionFlag, "description", DEFAULT_ACCESS_DESCRIPTION, fmt.Sprintf("Description of access (default: %s)", DEFAULT_ACCESS_DESCRIPTION)) + fs.BoolVar(&s.blockchainAccessFlag, "blockchain-access", DEFAULT_BLOCKCHAIN_ACCESS, fmt.Sprintf("Specify this flag to grant direct access to blockchain nodes (default: %t)", DEFAULT_BLOCKCHAIN_ACCESS)) + fs.BoolVar(&s.extendedMethodsFlag, "extended-methods", DEFAULT_EXTENDED_METHODS, fmt.Sprintf("Specify this flag to grant execution availability to not whitelisted methods (default: %t)", DEFAULT_EXTENDED_METHODS)) + fs.Int64Var(&s.PeriodDurationFlag, "period-duration", DEFAULT_PERIOD_DURATION, fmt.Sprintf("Access period duration in seconds (default: %d)", DEFAULT_PERIOD_DURATION)) + fs.Int64Var(&s.MaxCallsPerPeriodFlag, "max-calls-per-period", DEFAULT_MAX_CALLS_PER_PERIOD, fmt.Sprintf("Max available calls to node during the period (default: %d)", DEFAULT_MAX_CALLS_PER_PERIOD)) + } + + s.updateAccessCmd.Int64Var(&s.PeriodStartTsFlag, "period-start-ts", 0, "When period starts in unix timestamp format (default: now)") + s.updateAccessCmd.Int64Var(&s.CallsPerPeriodFlag, "calls-per-period", 0, "Current number of calls to node during the period (default: 0)") + + // 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 set 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 NodebalancerAppCli_OLD() { + stateCLI.populateCLI() + if len(os.Args) < 2 { + stateCLI.usage() + os.Exit(1) + } + + // Init bugout client + bc, err := CreateBugoutClient() + if err != nil { + log.Printf("An error occurred during Bugout client creation: %v", err) + os.Exit(1) + } + bugoutClient = bc + + // Parse subcommands and appropriate FlagSet + switch os.Args[1] { + case "generate-config": + stateCLI.generateConfigCmd.Parse(os.Args[2:]) + stateCLI.checkRequirements() + + case "add-access": + stateCLI.addAccessCmd.Parse(os.Args[2:]) + stateCLI.checkRequirements() + + proposedClientResourceData := ClientResourceData{ + UserID: stateCLI.userIDFlag, + AccessID: stateCLI.accessIDFlag, + Name: stateCLI.accessNameFlag, + Description: stateCLI.accessDescriptionFlag, + BlockchainAccess: stateCLI.blockchainAccessFlag, + ExtendedMethods: stateCLI.extendedMethodsFlag, + + PeriodDuration: stateCLI.PeriodDurationFlag, + PeriodStartTs: time.Now().Unix(), + MaxCallsPerPeriod: stateCLI.MaxCallsPerPeriodFlag, + CallsPerPeriod: 0, + + Type: BUGOUT_RESOURCE_TYPE_NODEBALANCER_ACCESS, + } + _, err := bugoutClient.Brood.FindUser( + NB_CONTROLLER_TOKEN, + map[string]string{ + "user_id": proposedClientResourceData.UserID, + "application_id": MOONSTREAM_APPLICATION_ID, + }, + ) + if err != nil { + fmt.Printf("User does not exists, err: %v\n", err) + os.Exit(1) + } + resource, err := bugoutClient.Brood.CreateResource(NB_CONTROLLER_TOKEN, MOONSTREAM_APPLICATION_ID, proposedClientResourceData) + if err != nil { + fmt.Printf("Unable to create user access, err: %v\n", err) + os.Exit(1) + } + resourceData, err := json.Marshal(resource.ResourceData) + if err != nil { + fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) + os.Exit(1) + } + var newUserAccess ClientAccess + err = json.Unmarshal(resourceData, &newUserAccess) + if err != nil { + fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) + os.Exit(1) + } + newUserAccess.ResourceID = resource.Id + userAccessJson, err := json.Marshal(newUserAccess) + if err != nil { + fmt.Printf("Unable to encode resource %s data interface to json, err: %v", resource.Id, err) + os.Exit(1) + } + fmt.Println(string(userAccessJson)) + + case "update-access": + stateCLI.updateAccessCmd.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( + NB_CONTROLLER_TOKEN, + MOONSTREAM_APPLICATION_ID, + queryParameters, + ) + if err != nil { + fmt.Printf("Unable to get Bugout resources, err: %v\n", err) + os.Exit(1) + } + + resourcesLen := len(resources.Resources) + if resourcesLen == 0 { + fmt.Printf("There are no access resource with provided user-id %s or access-id %s\n", stateCLI.userIDFlag, stateCLI.accessIDFlag) + os.Exit(1) + } + if resourcesLen > 1 { + fmt.Printf("There are several %d access resources with provided user-id %s or access-id %s\n", resourcesLen, stateCLI.userIDFlag, stateCLI.accessIDFlag) + os.Exit(1) + } + + resource := resources.Resources[0] + resourceData, err := json.Marshal(resource.ResourceData) + if err != nil { + fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) + os.Exit(1) + } + + var currentClientAccess ClientAccess + currentClientAccess.ResourceID = resource.Id + err = json.Unmarshal(resourceData, ¤tClientAccess.ClientResourceData) + if err != nil { + fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) + os.Exit(1) + } + + // TODO(kompotkot): Since we are using bool flags I moved with ugly solution. + // Let's find better one when have free time or will re-write flag Set. + update := make(map[string]interface{}) + if stateCLI.accessNameFlag != currentClientAccess.ClientResourceData.Name && stateCLI.accessNameFlag != DEFAULT_ACCESS_NAME { + update["name"] = stateCLI.accessNameFlag + } + if stateCLI.accessDescriptionFlag != currentClientAccess.ClientResourceData.Description && stateCLI.accessDescriptionFlag != DEFAULT_ACCESS_DESCRIPTION { + update["description"] = stateCLI.accessDescriptionFlag + } + if stateCLI.blockchainAccessFlag != currentClientAccess.ClientResourceData.BlockchainAccess && stateCLI.blockchainAccessFlag != DEFAULT_BLOCKCHAIN_ACCESS { + update["blockchain_access"] = stateCLI.blockchainAccessFlag + } + if stateCLI.extendedMethodsFlag != currentClientAccess.ClientResourceData.ExtendedMethods && stateCLI.extendedMethodsFlag != DEFAULT_EXTENDED_METHODS { + update["extended_methods"] = stateCLI.extendedMethodsFlag + } + if stateCLI.PeriodDurationFlag != currentClientAccess.ClientResourceData.PeriodDuration && stateCLI.PeriodDurationFlag != DEFAULT_PERIOD_DURATION { + update["period_duration"] = stateCLI.PeriodDurationFlag + } + if stateCLI.MaxCallsPerPeriodFlag != currentClientAccess.ClientResourceData.MaxCallsPerPeriod && stateCLI.MaxCallsPerPeriodFlag != DEFAULT_MAX_CALLS_PER_PERIOD { + update["max_calls_per_period"] = stateCLI.MaxCallsPerPeriodFlag + } + if stateCLI.PeriodStartTsFlag != currentClientAccess.ClientResourceData.PeriodStartTs && stateCLI.PeriodStartTsFlag != 0 { + update["period_start_ts"] = stateCLI.PeriodStartTsFlag + } + if stateCLI.CallsPerPeriodFlag != currentClientAccess.ClientResourceData.CallsPerPeriod && stateCLI.CallsPerPeriodFlag != 0 { + update["calls_per_period"] = stateCLI.CallsPerPeriodFlag + } + + updatedResource, err := bugoutClient.Brood.UpdateResource( + NB_CONTROLLER_TOKEN, + resource.Id, + update, + []string{}, + ) + if err != nil { + fmt.Printf("Unable to update Bugout resource, err: %v\n", err) + os.Exit(1) + } + + updatedResourceData, err := json.Marshal(updatedResource.ResourceData) + if err != nil { + fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) + os.Exit(1) + } + var updatedUserAccess ClientAccess + err = json.Unmarshal(updatedResourceData, &updatedUserAccess) + if err != nil { + fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) + os.Exit(1) + } + updatedUserAccess.ResourceID = updatedResource.Id + userAccessJson, err := json.Marshal(updatedUserAccess) + if err != nil { + fmt.Printf("Unable to marshal user access struct, err: %v\n", err) + os.Exit(1) + } + fmt.Println(string(userAccessJson)) + + 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( + NB_CONTROLLER_TOKEN, + MOONSTREAM_APPLICATION_ID, + queryParameters, + ) + if err != nil { + fmt.Printf("Unable to get Bugout resources, err: %v\n", err) + os.Exit(1) + } + + var userAccesses []ClientAccess + for _, resource := range resources.Resources { + deletedResource, err := bugoutClient.Brood.DeleteResource(NB_CONTROLLER_TOKEN, resource.Id) + if err != nil { + fmt.Printf("Unable to delete resource %s, err: %v\n", resource.Id, err) + continue + } + deletedResourceData, err := json.Marshal(deletedResource.ResourceData) + if err != nil { + fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) + continue + } + var deletedUserAccess ClientAccess + err = json.Unmarshal(deletedResourceData, &deletedUserAccess) + if err != nil { + fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) + continue + } + deletedUserAccess.ResourceID = deletedResource.Id + userAccesses = append(userAccesses, deletedUserAccess) + } + + userAccessesJson, err := json.Marshal(userAccesses) + if err != nil { + fmt.Printf("Unable to marshal user access struct, err: %v\n", err) + os.Exit(1) + } + fmt.Println(string(userAccessesJson)) + + case "server": + stateCLI.serverCmd.Parse(os.Args[2:]) + stateCLI.checkRequirements() + + CheckEnvVarSet() + + Server() + + case "users": + stateCLI.usersCmd.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( + NB_CONTROLLER_TOKEN, + MOONSTREAM_APPLICATION_ID, + queryParameters, + ) + if err != nil { + fmt.Printf("Unable to get Bugout resources, err: %v\n", err) + os.Exit(1) + } + + var clientAccesses []ClientAccess + + 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] { + resourceData, err := json.Marshal(resource.ResourceData) + if err != nil { + fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) + continue + } + var clientAccess ClientAccess + clientAccess.ResourceID = resource.Id + err = json.Unmarshal(resourceData, &clientAccess.ClientResourceData) + if err != nil { + fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) + continue + } + clientAccesses = append(clientAccesses, clientAccess) + } + userAccessesJson, err := json.Marshal(clientAccesses) + if err != nil { + fmt.Printf("Unable to marshal user accesses struct, err: %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", NB_VERSION) + + default: + stateCLI.usage() + os.Exit(1) + } +} diff --git a/nodebalancer/cmd/nodebalancer/clients.go b/nodebalancer/cmd/nodebalancer/clients.go index 56cb1e17..7064942a 100644 --- a/nodebalancer/cmd/nodebalancer/clients.go +++ b/nodebalancer/cmd/nodebalancer/clients.go @@ -1,10 +1,14 @@ package main import ( + "encoding/json" + "fmt" "log" "reflect" "sync" "time" + + "github.com/bugout-dev/bugout-go/pkg/brood" ) var ( @@ -188,3 +192,109 @@ func (cpool *ClientPool) CleanInactiveClientNodes() int { return cnt } + +// Creates new Bugout resource according to nodebalancer type to grant user or application access to call JSON RPC nodes +func AddNewAccess(accessId, userId, name, description string, blockchainAccess, extendedMethods bool, periodDuration, maxCallsPerPeriod uint, accessToken string) (*ClientAccess, error) { + _, findErr := bugoutClient.Brood.FindUser( + accessToken, + map[string]string{ + "user_id": userId, + "application_id": MOONSTREAM_APPLICATION_ID, + }, + ) + if findErr != nil { + return nil, fmt.Errorf("user does not exists, err: %v", findErr) + } + + proposedClientResourceData := ClientResourceData{ + AccessID: accessId, + UserID: userId, + Name: name, + Description: description, + BlockchainAccess: blockchainAccess, + ExtendedMethods: extendedMethods, + + PeriodDuration: int64(periodDuration), + PeriodStartTs: int64(time.Now().Unix()), + MaxCallsPerPeriod: int64(maxCallsPerPeriod), + CallsPerPeriod: 0, + + Type: BUGOUT_RESOURCE_TYPE_NODEBALANCER_ACCESS, + } + + resource, err := bugoutClient.Brood.CreateResource(accessToken, MOONSTREAM_APPLICATION_ID, proposedClientResourceData) + if err != nil { + return nil, fmt.Errorf("unable to create user access, err: %v", err) + } + resourceData, err := json.Marshal(resource.ResourceData) + if err != nil { + return nil, fmt.Errorf("unable to encode resource %s data interface to json, err: %v", resource.Id, err) + } + var newUserAccess ClientAccess + err = json.Unmarshal(resourceData, &newUserAccess) + if err != nil { + return nil, fmt.Errorf("unable to decode resource %s data json to structure, err: %v", resource.Id, err) + } + newUserAccess.ResourceID = resource.Id + + return &newUserAccess, nil +} + +func ShareAccess(resourceId, userId, holderType string, permissions []string, accessToken string) (*brood.ResourceHolders, error) { + resourceHolderPermissions, holdErr := bugoutClient.Brood.AddResourceHolderPermissions( + accessToken, resourceId, brood.ResourceHolder{ + Id: userId, + HolderType: holderType, + Permissions: permissions, + }, + ) + if holdErr != nil { + return nil, fmt.Errorf("unable to grant permissions to user with ID %s at resource with ID %s, err: %v", resourceId, userId, holdErr) + } + + return &resourceHolderPermissions, nil +} + +func GetAccesses(accessId, userId, accessToken string) (*brood.Resources, error) { + queryParameters := map[string]string{ + "type": BUGOUT_RESOURCE_TYPE_NODEBALANCER_ACCESS, + } + if userId != "" { + queryParameters["user_id"] = userId + } + if accessId != "" { + queryParameters["access_id"] = accessId + } + + resources, getResErr := bugoutClient.Brood.GetResources(accessToken, MOONSTREAM_APPLICATION_ID, queryParameters) + if getResErr != nil { + return nil, fmt.Errorf("unable to get Bugout resources, err: %v", getResErr) + } + + return &resources, nil +} + +func ParseResourceDataToClientAccess(resource brood.Resource) (*ClientAccess, error) { + resourceData, marErr := json.Marshal(resource.ResourceData) + if marErr != nil { + return nil, fmt.Errorf("unable to encode resource %s data interface to json, err: %v", resource.Id, marErr) + } + + var clientAccess ClientAccess + clientAccess.ResourceID = resource.Id + unmarErr := json.Unmarshal(resourceData, &clientAccess.ClientResourceData) + if unmarErr != nil { + return nil, fmt.Errorf("unable to decode resource %s data json to structure, err: %v", resource.Id, unmarErr) + } + + return &clientAccess, nil +} + +func DeleteAccess(resourceId, accessToken string) (*brood.Resource, error) { + del, delErr := bugoutClient.Brood.DeleteResource(accessToken, resourceId) + if delErr != nil { + return nil, fmt.Errorf("unable to delete resource, err: %v", delErr) + } + + return &del, nil +} diff --git a/nodebalancer/cmd/nodebalancer/configs.go b/nodebalancer/cmd/nodebalancer/configs.go index d3991328..c57194b6 100644 --- a/nodebalancer/cmd/nodebalancer/configs.go +++ b/nodebalancer/cmd/nodebalancer/configs.go @@ -23,15 +23,18 @@ var ( supportedBlockchains map[string]bool + bugoutClient *bugout.BugoutClient + // Bugout client // TODO(kompotkot): Find out why it cuts out the port - BUGOUT_BROOD_URL = "https://auth.bugout.dev" - // BUGOUT_BROOD_URL = os.Getenv("BUGOUT_BROOD_URL") + // BUGOUT_BROOD_URL = "https://auth.bugout.dev" + BUGOUT_BROOD_URL = os.Getenv("BUGOUT_BROOD_URL") NB_BUGOUT_TIMEOUT_SECONDS_RAW = os.Getenv("NB_BUGOUT_TIMEOUT_SECONDS") // Bugout and application configuration BUGOUT_AUTH_CALL_TIMEOUT = time.Second * 5 MOONSTREAM_APPLICATION_ID = os.Getenv("MOONSTREAM_APPLICATION_ID") + NB_CONTROLLER_USER_ID = os.Getenv("NB_CONTROLLER_USER_ID") NB_CONTROLLER_TOKEN = os.Getenv("NB_CONTROLLER_TOKEN") NB_CONTROLLER_ACCESS_ID = os.Getenv("NB_CONTROLLER_ACCESS_ID") MOONSTREAM_CORS_ALLOWED_ORIGINS = os.Getenv("MOONSTREAM_CORS_ALLOWED_ORIGINS") @@ -64,15 +67,15 @@ var ( DEFAULT_AUTOGENERATED_MAX_CALLS_PER_PERIOD = int64(1000) ) -func CreateBugoutClient() (bugout.BugoutClient, error) { +func CreateBugoutClient() (*bugout.BugoutClient, error) { bugoutTimeoutSeconds, err := strconv.Atoi(NB_BUGOUT_TIMEOUT_SECONDS_RAW) if err != nil { - return bugout.BugoutClient{}, fmt.Errorf("unable to parse environment variable as integer: %v", err) + return nil, fmt.Errorf("unable to parse environment variable as integer: %v", err) } NB_BUGOUT_TIMEOUT_SECONDS := time.Duration(bugoutTimeoutSeconds) * time.Second - broodClient := bugout.ClientBrood(BUGOUT_BROOD_URL, NB_BUGOUT_TIMEOUT_SECONDS) - return broodClient, nil + bugoutClient := bugout.ClientBrood(BUGOUT_BROOD_URL, NB_BUGOUT_TIMEOUT_SECONDS) + return &bugoutClient, nil } func CheckEnvVarSet() { diff --git a/nodebalancer/cmd/nodebalancer/main.go b/nodebalancer/cmd/nodebalancer/main.go index cee85c6c..c6dad82c 100644 --- a/nodebalancer/cmd/nodebalancer/main.go +++ b/nodebalancer/cmd/nodebalancer/main.go @@ -1,5 +1,13 @@ package main +import ( + "log" + "os" +) + func main() { - cli() + app := NodebalancerAppCli() + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } diff --git a/nodebalancer/cmd/nodebalancer/middleware.go b/nodebalancer/cmd/nodebalancer/middleware.go index 14881312..592490b7 100644 --- a/nodebalancer/cmd/nodebalancer/middleware.go +++ b/nodebalancer/cmd/nodebalancer/middleware.go @@ -234,52 +234,9 @@ func fetchClientAccessFromResources(accessID, authorizationToken string, tsNow i } if len(resources.Resources) == 0 { - if authorizationToken != "" { - // Generate new autogenerated access resource with default parameters and grant user permissions to work with it - user, err := bugoutClient.Brood.GetUser(authorizationToken) - if err != nil { - log.Printf("Unable to get user, err: %v", err) - return nil, fmt.Errorf("unable to find user with provided authorization token") - } - newResource, err := bugoutClient.Brood.CreateResource( - NB_CONTROLLER_TOKEN, MOONSTREAM_APPLICATION_ID, ClientResourceData{ - UserID: user.Id, - AccessID: uuid.New().String(), - Name: user.Username, - Description: "Autogenerated access ID", - BlockchainAccess: true, - ExtendedMethods: false, - - PeriodDuration: DEFAULT_AUTOGENERATED_PERIOD_DURATION, - PeriodStartTs: tsNow, - MaxCallsPerPeriod: DEFAULT_AUTOGENERATED_MAX_CALLS_PER_PERIOD, - CallsPerPeriod: 0, - - Type: BUGOUT_RESOURCE_TYPE_NODEBALANCER_ACCESS, - }, - ) - if err != nil { - log.Printf("Unable to create resource with autogenerated access for user with ID %s, err: %v", user.Id, err) - return nil, fmt.Errorf("unable to create resource with autogenerated access for user") - } - - resourceHolderPermissions, err := bugoutClient.Brood.AddResourceHolderPermissions( - NB_CONTROLLER_TOKEN, newResource.Id, brood.ResourceHolder{ - Id: user.Id, - HolderType: "user", - Permissions: DEFAULT_AUTOGENERATED_USER_PERMISSIONS, - }, - ) - if err != nil { - log.Printf("Unable to grant permissions to user with ID %s at resource with ID %s, err: %v", newResource.Id, user.Id, err) - return nil, fmt.Errorf("unable to create resource with autogenerated access for user") - } - - log.Printf("Created new resource with ID %s with autogenerated access for user with ID %s", resourceHolderPermissions.ResourceId, user.Id) - resources.Resources = append(resources.Resources, newResource) - } else { - return nil, fmt.Errorf("there are no provided access identifier") - } + // Generate new autogenerated access resource with default parameters and grant user permissions to work with it + // TODO(kompotkot): Not working because of permissions models change at Brood layer + return nil, fmt.Errorf("there are no provided access identifier") } else if len(resources.Resources) > 1 { // TODO(kompotkot): Write support of multiple resources, be careful, because NB_CONTROLLER has several resources return nil, fmt.Errorf("there are no provided access identifier") diff --git a/nodebalancer/go.mod b/nodebalancer/go.mod index 5726eb78..871d49ac 100644 --- a/nodebalancer/go.mod +++ b/nodebalancer/go.mod @@ -6,4 +6,11 @@ require ( github.com/bugout-dev/bugout-go v0.4.3 github.com/bugout-dev/humbug/go v0.0.0-20211206230955-57607cd2d205 github.com/google/uuid v1.3.0 + github.com/urfave/cli/v2 v2.27.5 +) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect ) diff --git a/nodebalancer/go.sum b/nodebalancer/go.sum index b8d5c645..99428eb2 100644 --- a/nodebalancer/go.sum +++ b/nodebalancer/go.sum @@ -12,6 +12,7 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -35,6 +36,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -142,6 +145,8 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -163,7 +168,11 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -282,6 +291,7 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/nodebalancer/sample.env b/nodebalancer/sample.env index d6458c49..efea6003 100644 --- a/nodebalancer/sample.env +++ b/nodebalancer/sample.env @@ -3,8 +3,9 @@ export BUGOUT_BROOD_URL="https://auth.bugout.dev" export NB_BUGOUT_TIMEOUT_SECONDS=15 export MOONSTREAM_APPLICATION_ID="" export MOONSTREAM_CORS_ALLOWED_ORIGINS="http://localhost:3000,https://moonstream.to,https://portal.moonstream.to" -export NB_CONTROLLER_TOKEN="" -export NB_CONTROLLER_ACCESS_ID="" +export NB_CONTROLLER_USER_ID="" +export NB_CONTROLLER_TOKEN="" +export NB_CONTROLLER_ACCESS_ID="" # Error humbug reporter export HUMBUG_REPORTER_NODE_BALANCER_TOKEN=""