Skip to content

Commit 96a1f6e

Browse files
committed
(choria-io#2029) Extract some common code and reuse in CLI
Also improve the plugins pack cli. The external agents provider will check frequently at first for new agents then settle into a 30s kind of interval, with jitter. This optimise bootup to deliver agents quickly but with a more relaxed settled state Signed-off-by: R.I.Pienaar <rip@devco.net>
1 parent f41712e commit 96a1f6e

File tree

7 files changed

+120
-44
lines changed

7 files changed

+120
-44
lines changed

aagent/watchers/pluginswatcher/plugins.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,6 @@ var stateNames = map[State]string{
5454
Unchanged: "unchanged",
5555
}
5656

57-
type Specification struct {
58-
Plugins []byte `json:"plugins"`
59-
Signature string `json:"signature,omitempty"`
60-
}
61-
6257
type ManagedPlugin struct {
6358
Name string `json:"name" yaml:"name"`
6459
NamePrefix string `json:"-" yaml:"-"`
@@ -253,7 +248,7 @@ func (w *Watcher) watch(ctx context.Context) (state State, err error) {
253248
}
254249
}
255250

256-
w.Warnf("Deploying Choria Autonomous Agent %s from %s", m.Name, m.Source)
251+
w.Warnf("Deploying plugin %s from %s info %s", m.Name, m.Source, targetDir)
257252

258253
err = os.MkdirAll(targetDir, 0700)
259254
if err != nil {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) 2023, R.I. Pienaar and the Choria Project contributors
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package machines
6+
7+
import (
8+
"crypto/ed25519"
9+
"encoding/hex"
10+
"encoding/json"
11+
12+
iu "github.com/choria-io/go-choria/internal/util"
13+
)
14+
15+
// Specification holds []ManagedPlugin marshaled to JSON with an optional ed25519 signature
16+
type Specification struct {
17+
Plugins []byte `json:"plugins"`
18+
Signature string `json:"signature,omitempty"`
19+
}
20+
21+
// Encode sets the signature and Marshals the specification to JSON, if key is empty signature is not made
22+
func (s *Specification) Encode(key string) ([]byte, error) {
23+
var pk ed25519.PrivateKey
24+
var err error
25+
26+
data, err := json.Marshal(s)
27+
if err != nil {
28+
return nil, err
29+
}
30+
31+
if key != "" {
32+
if iu.IsEncodedEd25519KeyString(key) {
33+
pk, err = hex.DecodeString(key)
34+
} else {
35+
_, pk, err = iu.Ed25519KeyPairFromSeedFile(key)
36+
}
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
sig, err := iu.Ed25519Sign(pk, data)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
s.Signature = hex.EncodeToString(sig)
47+
}
48+
49+
return json.Marshal(s)
50+
}

broker/network/choria_auth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ func (a *ChoriaAuth) cachedEd25519Token(token string) (ed25519.PublicKey, error)
663663
func (a *ChoriaAuth) parseServerJWTWithSigners(jwts string) (claims *tokens.ServerClaims, err error) {
664664
for _, s := range a.serverJwtSigners {
665665
// its a token
666-
if tokens.IsEncodedEd25519Key([]byte(s)) {
666+
if util.IsEncodedEd25519KeyString(s) {
667667
var pk ed25519.PublicKey
668668
pk, err = a.cachedEd25519Token(s)
669669
if err != nil {
@@ -765,7 +765,7 @@ func (a *ChoriaAuth) parseServerJWT(jwts string) (claims *tokens.ServerClaims, e
765765
func (a *ChoriaAuth) parseClientJWTWithSigners(jwts string) (claims *tokens.ClientIDClaims, err error) {
766766
for _, s := range a.clientJwtSigners {
767767
// its a token
768-
if tokens.IsEncodedEd25519Key([]byte(s)) {
768+
if util.IsEncodedEd25519KeyString(s) {
769769
var pk ed25519.PublicKey
770770
pk, err = a.cachedEd25519Token(s)
771771
if err != nil {

cmd/machine_plugins.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2023, R.I. Pienaar and the Choria Project contributors
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package cmd
6+
7+
import (
8+
"sync"
9+
)
10+
11+
type machinePluginsCommand struct {
12+
command
13+
}
14+
15+
func (p *machinePluginsCommand) Setup() (err error) {
16+
if machine, ok := cmdWithFullCommand("machine"); ok {
17+
p.cmd = machine.Cmd().Command("plugins", "Manage specifications for the plugins watcher")
18+
}
19+
return nil
20+
}
21+
22+
func (p *machinePluginsCommand) Configure() error {
23+
return nil
24+
}
25+
26+
func (p *machinePluginsCommand) Run(wg *sync.WaitGroup) (err error) {
27+
defer wg.Done()
28+
29+
return nil
30+
}
31+
32+
func init() {
33+
cli.commands = append(cli.commands, &machinePluginsCommand{})
34+
}

cmd/machine_pack.go renamed to cmd/machine_plugins_pack.go

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
package cmd
66

77
import (
8-
"crypto/ed25519"
9-
"encoding/hex"
108
"encoding/json"
119
"fmt"
1210
"os"
@@ -18,18 +16,18 @@ import (
1816
"github.com/sirupsen/logrus"
1917
)
2018

21-
type mPackCommand struct {
19+
type mPluginsPackCommand struct {
2220
command
23-
machines string
24-
key string
25-
out string
26-
force bool
21+
source string
22+
key string
23+
out string
24+
force bool
2725
}
2826

29-
func (r *mPackCommand) Setup() (err error) {
30-
if machine, ok := cmdWithFullCommand("machine"); ok {
31-
r.cmd = machine.Cmd().Command("plugins", "Encodes and signs data for the plugins watcher")
32-
r.cmd.Arg("source", "File containing the plugins definition").Required().ExistingFileVar(&r.machines)
27+
func (r *mPluginsPackCommand) Setup() (err error) {
28+
if machine, ok := cmdWithFullCommand("machine plugins"); ok {
29+
r.cmd = machine.Cmd().Command("pack", "Encodes and signs data for the plugins watcher")
30+
r.cmd.Arg("source", "File containing the plugins definition").Required().ExistingFileVar(&r.source)
3331
r.cmd.Arg("key", "The ed25519 private key to encode with").StringVar(&r.key)
3432
r.cmd.Flag("force", "Do not warn about no ed25519 key and support writing empty files").BoolVar(&r.force)
3533
r.cmd.Flag("output", "Write result to a file").StringVar(&r.out)
@@ -38,7 +36,7 @@ func (r *mPackCommand) Setup() (err error) {
3836
return nil
3937
}
4038

41-
func (r *mPackCommand) Configure() error {
39+
func (r *mPluginsPackCommand) Configure() error {
4240
if debug {
4341
logrus.SetOutput(os.Stdout)
4442
logrus.SetLevel(logrus.DebugLevel)
@@ -56,10 +54,10 @@ func (r *mPackCommand) Configure() error {
5654
return err
5755
}
5856

59-
func (r *mPackCommand) Run(wg *sync.WaitGroup) (err error) {
57+
func (r *mPluginsPackCommand) Run(wg *sync.WaitGroup) (err error) {
6058
defer wg.Done()
6159

62-
data, err := os.ReadFile(r.machines)
60+
data, err := os.ReadFile(r.source)
6361
if err != nil {
6462
return err
6563
}
@@ -74,25 +72,12 @@ func (r *mPackCommand) Run(wg *sync.WaitGroup) (err error) {
7472
return fmt.Errorf("no plugins listed in specification, use --force to write an empty list")
7573
}
7674

77-
spec := watcher.Specification{Plugins: data}
78-
79-
if r.key != "" {
80-
var key []byte
81-
if iu.FileExist(r.key) {
82-
key, err = os.ReadFile(r.key)
83-
} else {
84-
key, err = hex.DecodeString(r.key)
85-
}
86-
if err != nil {
87-
return err
88-
}
89-
90-
spec.Signature = hex.EncodeToString(ed25519.Sign(key, data))
91-
} else if !r.force {
75+
if r.key == "" && !r.force {
9276
logrus.Warn("No ed25519 private key given, encoding without signing")
9377
}
9478

95-
j, err := json.Marshal(spec)
79+
spec := &watcher.Specification{Plugins: data}
80+
j, err := spec.Encode(r.key)
9681
if err != nil {
9782
return err
9883
}
@@ -110,5 +95,5 @@ func (r *mPackCommand) Run(wg *sync.WaitGroup) (err error) {
11095
}
11196

11297
func init() {
113-
cli.commands = append(cli.commands, &mPackCommand{})
98+
cli.commands = append(cli.commands, &mPluginsPackCommand{})
11499
}

internal/util/ed25519.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"encoding/hex"
1111
"fmt"
1212
"os"
13+
14+
"github.com/choria-io/tokens"
1315
)
1416

1517
func Ed25519SignWithSeedFile(f string, msg []byte) ([]byte, error) {
@@ -78,3 +80,11 @@ func Ed25519KeyPairToFile(f string) (ed25519.PublicKey, ed25519.PrivateKey, erro
7880

7981
return pubK, priK, nil
8082
}
83+
84+
func IsEncodedEd25519Key(k []byte) bool {
85+
return tokens.IsEncodedEd25519Key(k)
86+
}
87+
88+
func IsEncodedEd25519KeyString(k string) bool {
89+
return tokens.IsEncodedEd25519Key([]byte(k))
90+
}

providers/agent/mcorpc/external/provider.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"sync"
1515
"time"
1616

17+
"github.com/choria-io/go-choria/backoff"
1718
"github.com/sirupsen/logrus"
1819

1920
"github.com/choria-io/go-choria/config"
@@ -26,10 +27,8 @@ import (
2627
var (
2728
// agents we do not ever wish to load from external agents
2829
denyList = []string{"rpcutil", "choria_util", "choria_provision", "choria_registry", "discovery", "scout"}
29-
// how frequently agents are reconciled
30-
watchInterval = time.Minute
3130
// we only consider ddl files modified longer than this ago for reconciliation
32-
fileChangeGrace = 20 * time.Second
31+
fileChangeGrace = 5 * time.Second
3332
)
3433

3534
// Provider is a Choria Agent Provider that supports calling agents external to the
@@ -211,8 +210,8 @@ func (p *Provider) watchAgents(ctx context.Context, mgr server.AgentManager, con
211210
p.log.Errorf("Initial agent reconcile failed: %v", err)
212211
}
213212

214-
ticker := time.NewTicker(watchInterval)
215-
p.log.Debugf("Watching for agent updates every %v", watchInterval)
213+
count := 1
214+
ticker := time.NewTicker(backoff.TwentySec.Duration(count))
216215

217216
for {
218217
select {
@@ -222,6 +221,9 @@ func (p *Provider) watchAgents(ctx context.Context, mgr server.AgentManager, con
222221
p.log.Errorf("Reconciling agents failed: %v", err)
223222
}
224223

224+
count++
225+
ticker.Reset(backoff.TwentySec.Duration(count))
226+
225227
case <-ctx.Done():
226228
return
227229
}

0 commit comments

Comments
 (0)