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 }