github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/cmd/governor_api.go (about)

     1  // Copyright (c) 2021-2022, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package cmd
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"os"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/choria-io/go-choria/config"
    16  	"github.com/choria-io/go-choria/internal/util"
    17  	governor "github.com/choria-io/go-choria/providers/governor/streams"
    18  	"github.com/nats-io/jsm.go"
    19  )
    20  
    21  type tGovAPICommand struct {
    22  	command
    23  	update bool
    24  	list   bool
    25  	delete bool
    26  	check  bool
    27  
    28  	collective string
    29  	name       string
    30  	limit      int64
    31  	expire     int
    32  	replicas   int
    33  	force      bool
    34  }
    35  
    36  func (g *tGovAPICommand) Setup() (err error) {
    37  	if gov, ok := cmdWithFullCommand("governor"); ok {
    38  		g.cmd = gov.Cmd().Command("api", "API to manage Governors via JSON inputs and outputs").Hidden()
    39  		g.cmd.Flag("ensure", "Creates or Updates the governor based on supplied configuration").BoolVar(&g.update)
    40  		g.cmd.Flag("delete", "Deletes a specific governor").PlaceHolder("GOVERNOR").BoolVar(&g.delete)
    41  		g.cmd.Flag("list", "List known governors").BoolVar(&g.list)
    42  		g.cmd.Flag("check", "Checks if the API is available").BoolVar(&g.check)
    43  
    44  		g.cmd.Flag("name", "Governor name").PlaceHolder("NAME").StringVar(&g.name)
    45  		g.cmd.Flag("capacity", "Governor capacity").PlaceHolder("CAPACITY").Int64Var(&g.limit)
    46  		g.cmd.Flag("expire", "How long before entries expire from the governor").PlaceHolder("SECONDS").IntVar(&g.expire)
    47  		g.cmd.Flag("replicas", "How many replicas to store on the server").PlaceHolder("REPLICAS").IntVar(&g.replicas)
    48  		g.cmd.Flag("collective", "The sub-collective to install the Governor in").PlaceHolder("COLLECTIVE").StringVar(&g.collective)
    49  		g.cmd.Flag("force", "Force changes that require the governor to be recreated").BoolVar(&g.force)
    50  	}
    51  
    52  	return nil
    53  }
    54  
    55  func (g *tGovAPICommand) Configure() error {
    56  	if os.Getuid() == 0 {
    57  		cfg, err = config.NewSystemConfig(configFile, true)
    58  		if err != nil {
    59  			g.fail("config failed: %s", err)
    60  		}
    61  		cfg.LogLevel = "error"
    62  	} else {
    63  		err = commonConfigure()
    64  		if err != nil {
    65  			g.fail("config failed: %s", err)
    66  		}
    67  	}
    68  
    69  	return nil
    70  }
    71  
    72  func (g *tGovAPICommand) Run(wg *sync.WaitGroup) (err error) {
    73  	defer wg.Done()
    74  
    75  	switch {
    76  	case g.check:
    77  		g.jsonDump(map[string]string{"api": "ok"})
    78  	case g.update:
    79  		g.updateCmd()
    80  	case g.delete:
    81  		g.deleteCmd()
    82  	case g.list:
    83  		g.listCmd()
    84  	default:
    85  		g.fail("no command given")
    86  	}
    87  
    88  	return nil
    89  }
    90  
    91  func (g *tGovAPICommand) updateCmd() {
    92  	switch {
    93  	case g.name == "":
    94  		g.fail("name required")
    95  	case g.limit == 0:
    96  		g.fail("capacity can not be 0")
    97  	case g.expire < 0:
    98  		g.fail("expire must be >= 0")
    99  	case g.replicas < 1 || g.replicas > 5:
   100  		g.fail("replicas should be 1-5")
   101  	case g.collective == "":
   102  		g.fail("collective is required")
   103  	}
   104  
   105  	gov, _, err := c.NewGovernorManager(ctx, g.name, uint64(g.limit), time.Duration(g.expire)*time.Second, uint(g.replicas), true, nil, governor.WithSubject(util.GovernorSubject(g.name, g.collective)))
   106  	if err != nil {
   107  		g.fail("update failed: %s", err)
   108  	}
   109  
   110  	parts := strings.Split(gov.Subject(), ".")
   111  
   112  	g.jsonDump(map[string]any{
   113  		"name":       gov.Name(),
   114  		"capacity":   gov.Limit(),
   115  		"expire":     gov.MaxAge().Seconds(),
   116  		"replicas":   gov.Replicas(),
   117  		"collective": parts[0],
   118  	})
   119  }
   120  
   121  func (g *tGovAPICommand) deleteCmd() {
   122  	if g.name == "" {
   123  		g.fail("no name given")
   124  	}
   125  
   126  	conn, err := c.NewConnector(ctx, c.MiddlewareServers, fmt.Sprintf("governor manager: %s", "governor_list"), c.Logger("governor"))
   127  	if err != nil {
   128  		g.fail("connection failed: %s", err)
   129  	}
   130  
   131  	mgr, err := jsm.New(conn.Nats())
   132  	if err != nil {
   133  		g.fail("connection failed: %s", err)
   134  	}
   135  
   136  	str, err := mgr.LoadStream(fmt.Sprintf("GOVERNOR_%s", g.name))
   137  	if err != nil {
   138  		if jsm.IsNatsError(err, 10059) {
   139  			return
   140  		}
   141  		g.fail("could not find governor: %s", err)
   142  	}
   143  
   144  	err = str.Delete()
   145  	if err != nil {
   146  		g.fail("delete failed: %s", err)
   147  	}
   148  }
   149  
   150  func (g *tGovAPICommand) listCmd() {
   151  	type gov struct {
   152  		Name       string `json:"name"`
   153  		Capacity   int64  `json:"capacity"`
   154  		Expire     int    `json:"expire"`
   155  		Replicas   int    `json:"replicas"`
   156  		Collective string `json:"collective"`
   157  	}
   158  
   159  	conn, err := c.NewConnector(ctx, c.MiddlewareServers, fmt.Sprintf("governor manager: %s", "governor_list"), c.Logger("governor"))
   160  	if err != nil {
   161  		g.fail("connection failed: %s", err)
   162  	}
   163  
   164  	known, err := governor.List(conn.Nats(), c.Config.MainCollective)
   165  	if err != nil {
   166  		g.fail("connection failed: %s", err)
   167  	}
   168  
   169  	var govs = make([]gov, len(known))
   170  	for i := 0; i < len(known); i++ {
   171  		name := strings.TrimPrefix(known[i], "GOVERNOR_")
   172  
   173  		mgr, _, err := c.NewGovernorManager(ctx, name, 0, 0, 1, false, conn)
   174  		if err != nil {
   175  			g.fail("loading failed: %s", err)
   176  		}
   177  
   178  		parts := strings.Split(mgr.Subject(), ".")
   179  		govs[i] = gov{
   180  			Name:       name,
   181  			Capacity:   mgr.Limit(),
   182  			Expire:     int(mgr.MaxAge().Seconds()),
   183  			Replicas:   mgr.Replicas(),
   184  			Collective: parts[0],
   185  		}
   186  	}
   187  
   188  	g.jsonDump(govs)
   189  }
   190  
   191  func (g *tGovAPICommand) fail(format string, a ...any) {
   192  	g.jsonDump(map[string]string{
   193  		"error": fmt.Sprintf(format, a...),
   194  	})
   195  
   196  	os.Exit(1)
   197  }
   198  
   199  func (g *tGovAPICommand) jsonDump(d any) {
   200  	j, err := json.Marshal(d)
   201  	if err != nil {
   202  		panic(err)
   203  	}
   204  
   205  	fmt.Println(string(j))
   206  }
   207  func init() {
   208  	cli.commands = append(cli.commands, &tGovAPICommand{})
   209  }