diff --git a/aagent/watchers/archivewatcher/archive.go b/aagent/watchers/archivewatcher/archive.go index e08a36b50..41454cbcc 100644 --- a/aagent/watchers/archivewatcher/archive.go +++ b/aagent/watchers/archivewatcher/archive.go @@ -369,7 +369,6 @@ func (w *Watcher) untar(s io.Reader, t string) error { } nfo := header.FileInfo() - w.Debugf("untar: %s", path) if nfo.IsDir() { err = os.MkdirAll(path, nfo.Mode()) if err != nil { diff --git a/aagent/watchers/pluginswatcher/plugins.go b/aagent/watchers/pluginswatcher/plugins.go index 1030047c7..f32f26302 100644 --- a/aagent/watchers/pluginswatcher/plugins.go +++ b/aagent/watchers/pluginswatcher/plugins.go @@ -57,12 +57,12 @@ type ManagedPlugin struct { Name string `json:"name" yaml:"name"` NamePrefix string `json:"-" yaml:"-"` Source string `json:"source" yaml:"source"` - Username string `json:"username" yaml:"username"` - Password string `json:"password" yaml:"password"` + Username string `json:"username,omitempty" yaml:"username"` + Password string `json:"password,omitempty" yaml:"password"` ContentChecksumsChecksum string `json:"verify_checksum" yaml:"verify_checksum" mapstructure:"verify_checksum"` ArchiveChecksum string `json:"checksum" yaml:"checksum" mapstructure:"checksum"` - Matcher string `json:"match" yaml:"match" mapstructure:"match"` - Governor string `json:"governor" yaml:"governor" mapstructure:"governor"` + Matcher string `json:"match,omitempty" yaml:"match" mapstructure:"match"` + Governor string `json:"governor,omitempty" yaml:"governor" mapstructure:"governor"` Interval string `json:"-"` Target string `json:"-"` @@ -364,7 +364,10 @@ func (w *Watcher) purgeUnknownPlugins(ctx context.Context, desired []*ManagedPlu } w.Debugf("Sleeping for 2 seconds to allow manager to exit") - iu.InterruptibleSleep(ctx, 2*time.Second) + err = iu.InterruptibleSleep(ctx, 2*time.Second) + if err != nil { + return false, err + } target = w.targetDirForManagedPlugin(m) err = os.RemoveAll(target) diff --git a/cmd/machine_plugins_add.go b/cmd/machine_plugins_add.go new file mode 100644 index 000000000..0529d0f9a --- /dev/null +++ b/cmd/machine_plugins_add.go @@ -0,0 +1,150 @@ +// Copyright (c) 2023, R.I. Pienaar and the Choria Project contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "encoding/json" + "fmt" + "net/url" + "os" + "strings" + "sync" + + "github.com/choria-io/go-choria/aagent/watchers/pluginswatcher" + "github.com/choria-io/go-choria/config" + iu "github.com/choria-io/go-choria/internal/util" + "github.com/sirupsen/logrus" +) + +type mPluginsAddCommand struct { + command + + matcher string + matcherIsSet bool + governor string + governorIsSet bool + pluginUrl *url.URL + manifest string + checksum string + verifyChecksum string + name string +} + +func (c *mPluginsAddCommand) Setup() (err error) { + if machine, ok := cmdWithFullCommand("machine plugins"); ok { + c.cmd = machine.Cmd().Command("add", "Add or Update a reference to a plugin to the Plugins Watcher manifest") + c.cmd.Arg("name", "Unique name for this plugin").Required().StringVar(&c.name) + c.cmd.Arg("plugin", "The URL to the tar file holding the plugin").Required().URLVar(&c.pluginUrl) + c.cmd.Arg("checksum", "SHA256 checksum of the archive").Required().StringVar(&c.checksum) + c.cmd.Arg("verify-checksum", "SHA256 checksum of the verification file").Required().StringVar(&c.verifyChecksum) + c.cmd.Flag("manifest", "Path to the manifest to edit").Default("plugins.json").StringVar(&c.manifest) + c.cmd.Flag("matcher", "Limit deployment using an expression").IsSetByUser(&c.matcherIsSet).StringVar(&c.matcher) + c.cmd.Flag("governor", "Limit deployment using a Governor").IsSetByUser(&c.governorIsSet).StringVar(&c.governor) + } + + return nil +} + +func init() { + cli.commands = append(cli.commands, &mPluginsAddCommand{}) +} + +func (c *mPluginsAddCommand) Configure() error { + if debug { + logrus.SetOutput(os.Stdout) + logrus.SetLevel(logrus.DebugLevel) + logrus.Debug("Logging at debug level due to CLI override") + } + + cfg, err = config.NewDefaultConfig() + if err != nil { + return err + } + + cfg.Choria.SecurityProvider = "file" + cfg.DisableSecurityProviderVerify = true + + return err +} + +func (c *mPluginsAddCommand) Run(wg *sync.WaitGroup) (err error) { + defer wg.Done() + + var manifest []*pluginswatcher.ManagedPlugin + + if iu.FileExist(c.manifest) { + dat, err := os.ReadFile(c.manifest) + if err != nil { + return err + } + + err = json.Unmarshal(dat, &manifest) + if err != nil { + return err + } + } + + user := c.pluginUrl.User + c.pluginUrl.User = nil + var found bool + + for _, p := range manifest { + if p.Name == c.name { + c.updatePlugin(p, user) + found = true + + fmt.Printf("Updating plugin %s\n", p.Name) + } + } + + if !found { + p := &pluginswatcher.ManagedPlugin{} + c.updatePlugin(p, user) + + name := strings.Split(c.pluginUrl.Path[1:], "-") + if len(name) == 0 { + return fmt.Errorf("could not determine name, please pass --name") + } + p.Name = name[0] + + fmt.Printf("Adding plugin %s\n", p.Name) + manifest = append(manifest, p) + } + + dat, err := json.MarshalIndent(manifest, "", " ") + if err != nil { + return err + } + + err = os.WriteFile(c.manifest, dat, 0600) + if err != nil { + return err + } + + fmt.Printf("Use the pack command to sign the %s manifest\n", c.manifest) + + return nil +} + +func (c *mPluginsAddCommand) updatePlugin(plugin *pluginswatcher.ManagedPlugin, user *url.Userinfo) { + plugin.Source = c.pluginUrl.String() + plugin.ArchiveChecksum = c.checksum + plugin.ContentChecksumsChecksum = c.verifyChecksum + + if c.governorIsSet { + plugin.Governor = c.governor + } + if c.matcherIsSet { + plugin.Matcher = c.matcher + } + if user != nil { + plugin.Username = user.Username() + plugin.Password, _ = user.Password() + } + + if c.name != "" { + plugin.Name = c.name + } +} diff --git a/cmd/machine_plugins_pack.go b/cmd/machine_plugins_pack.go index bdb01b763..272b11799 100644 --- a/cmd/machine_plugins_pack.go +++ b/cmd/machine_plugins_pack.go @@ -27,7 +27,7 @@ type mPluginsPackCommand struct { func (r *mPluginsPackCommand) Setup() (err error) { if machine, ok := cmdWithFullCommand("machine plugins"); ok { - r.cmd = machine.Cmd().Command("pack", "Encodes and signs data for the plugins watcher") + r.cmd = machine.Cmd().Command("pack", "Encodes and signs data for the plugins Watcher") r.cmd.Arg("source", "File containing the plugins definition").Required().ExistingFileVar(&r.source) r.cmd.Arg("seed", "The ed25519 seed file to encode with").StringVar(&r.key) r.cmd.Flag("force", "Do not warn about no ed25519 key and support writing empty files").BoolVar(&r.force) diff --git a/cmd/machine_plugins_rm.go b/cmd/machine_plugins_rm.go new file mode 100644 index 000000000..1fb4355b0 --- /dev/null +++ b/cmd/machine_plugins_rm.go @@ -0,0 +1,102 @@ +// Copyright (c) 2023, R.I. Pienaar and the Choria Project contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "encoding/json" + "fmt" + "os" + "sync" + + "github.com/choria-io/go-choria/aagent/watchers/pluginswatcher" + "github.com/choria-io/go-choria/config" + "github.com/sirupsen/logrus" +) + +type mPluginsRmCommand struct { + command + + name string + manifest string +} + +func (c *mPluginsRmCommand) Setup() (err error) { + if machine, ok := cmdWithFullCommand("machine plugins"); ok { + c.cmd = machine.Cmd().Command("rm", "Removes references to a plugin from the Plugins Watcher manifest") + c.cmd.Arg("name", "Plugin name to remove from the manifest").Required().StringVar(&c.name) + c.cmd.Flag("manifest", "Path to the manifest to edit").Default("plugins.json").ExistingFileVar(&c.manifest) + } + + return nil +} + +func init() { + cli.commands = append(cli.commands, &mPluginsRmCommand{}) +} + +func (c *mPluginsRmCommand) Configure() error { + if debug { + logrus.SetOutput(os.Stdout) + logrus.SetLevel(logrus.DebugLevel) + logrus.Debug("Logging at debug level due to CLI override") + } + + cfg, err = config.NewDefaultConfig() + if err != nil { + return err + } + + cfg.Choria.SecurityProvider = "file" + cfg.DisableSecurityProviderVerify = true + + return err +} + +func (c *mPluginsRmCommand) Run(wg *sync.WaitGroup) (err error) { + defer wg.Done() + + var manifest []*pluginswatcher.ManagedPlugin + + dat, err := os.ReadFile(c.manifest) + if err != nil { + return err + } + + err = json.Unmarshal(dat, &manifest) + if err != nil { + return err + } + + var found bool + newManifest := []*pluginswatcher.ManagedPlugin{} + + for _, p := range manifest { + if p.Name == c.name { + fmt.Printf("Removing plugin %s\n", c.name) + found = true + continue + } + + newManifest = append(newManifest, p) + } + + if !found { + return fmt.Errorf("no plugin named %s found", c.name) + } + + dat, err = json.MarshalIndent(newManifest, "", " ") + if err != nil { + return err + } + + err = os.WriteFile(c.manifest, dat, 0600) + if err != nil { + return err + } + + fmt.Printf("Use the pack command to sign the %s manifest\n", c.manifest) + + return nil +}