crowdsec/pkg/cwhub/enable.go
mmetc 4a6fd338e0
replace 'timeout' helper with async python script; allow hub preload in func tests; improve item removal (#2591)
* replace 'timeout' helper with async python script; allow hub preload in func tests; improve item removal
* func tests: cscli hub update/upgrade
* docker test update
* Update docker entrypoint to disable items with --force

The --force flag was not transmitted to cscli, but is required after the hub refact
to disable items inside installed collections
2023-11-14 17:36:07 +01:00

194 lines
5.1 KiB
Go

package cwhub
// Enable/disable items already downloaded
import (
"fmt"
"os"
"path/filepath"
log "github.com/sirupsen/logrus"
)
// enable creates a symlink between actual config file at hub.HubDir and hub.ConfigDir
// Handles collections recursively
func (i *Item) enable() error {
parentDir := filepath.Clean(i.hub.local.InstallDir + "/" + i.Type + "/" + i.Stage + "/")
// create directories if needed
if i.Installed {
if i.Tainted {
return fmt.Errorf("%s is tainted, won't enable unless --force", i.Name)
}
if i.IsLocal() {
return fmt.Errorf("%s is local, won't enable", i.Name)
}
// if it's a collection, check sub-items even if the collection file itself is up-to-date
if i.UpToDate && !i.HasSubItems() {
log.Tracef("%s is installed and up-to-date, skip.", i.Name)
return nil
}
}
if _, err := os.Stat(parentDir); os.IsNotExist(err) {
log.Infof("%s doesn't exist, create", parentDir)
if err = os.MkdirAll(parentDir, os.ModePerm); err != nil {
return fmt.Errorf("while creating directory: %w", err)
}
}
// install sub-items if any
for _, sub := range i.SubItems() {
if err := sub.enable(); err != nil {
return fmt.Errorf("while installing %s: %w", sub.Name, err)
}
}
// check if file already exists where it should in configdir (eg /etc/crowdsec/collections/)
if _, err := os.Lstat(parentDir + "/" + i.FileName); !os.IsNotExist(err) {
log.Infof("%s already exists.", parentDir+"/"+i.FileName)
return nil
}
// hub.ConfigDir + target.RemotePath
srcPath, err := filepath.Abs(i.hub.local.HubDir + "/" + i.RemotePath)
if err != nil {
return fmt.Errorf("while getting source path: %w", err)
}
dstPath, err := filepath.Abs(parentDir + "/" + i.FileName)
if err != nil {
return fmt.Errorf("while getting destination path: %w", err)
}
if err = os.Symlink(srcPath, dstPath); err != nil {
return fmt.Errorf("while creating symlink from %s to %s: %w", srcPath, dstPath, err)
}
log.Infof("Enabled %s: %s", i.Type, i.Name)
i.Installed = true
return nil
}
// purge removes the actual config file that was downloaded
func (i *Item) purge() error {
if !i.Downloaded {
log.Infof("removing %s: not downloaded -- no need to remove", i.Name)
return nil
}
itempath := i.hub.local.HubDir + "/" + i.RemotePath
// disable hub file
if err := os.Remove(itempath); err != nil {
if os.IsNotExist(err) {
log.Debugf("%s doesn't exist, no need to remove", itempath)
return nil
}
return fmt.Errorf("while removing file: %w", err)
}
i.Downloaded = false
log.Infof("Removed source file [%s]: %s", i.Name, itempath)
return nil
}
// disable removes the symlink to the downloaded content, also removes the content if purge is true
func (i *Item) disable(purge bool, force bool) error {
// XXX: should return the number of disabled/purged items to inform the upper layer whether to reload or not
var err error
if i.IsLocal() {
return fmt.Errorf("%s isn't managed by hub. Please delete manually", i.Name)
}
if i.Tainted && !force {
return fmt.Errorf("%s is tainted, use '--force' to overwrite", i.Name)
}
// disable sub-items if any - it's a collection
for _, sub := range i.SubItems() {
// check if the item doesn't belong to another collection before removing it
removeSub := true
for _, collection := range sub.BelongsToCollections {
if collection != i.Name {
removeSub = false
break
}
}
if removeSub {
if err = sub.disable(purge, force); err != nil {
return fmt.Errorf("while disabling %s: %w", sub.Name, err)
}
} else {
log.Infof("%s was not removed because it belongs to another collection", sub.Name)
}
}
if !i.Installed && !purge {
log.Infof("removing %s: not installed -- no need to remove", i.Name)
return nil
}
syml, err := filepath.Abs(i.hub.local.InstallDir + "/" + i.Type + "/" + i.Stage + "/" + i.FileName)
if err != nil {
return err
}
stat, err := os.Lstat(syml)
if os.IsNotExist(err) {
// we only accept to "delete" non existing items if it's a forced purge
if !purge && !force {
return fmt.Errorf("can't delete %s: %s doesn't exist", i.Name, syml)
}
} else {
// if it's managed by hub, it's a symlink to csconfig.GConfig.hub.HubDir / ...
if stat.Mode()&os.ModeSymlink == 0 {
log.Warningf("%s (%s) isn't a symlink, can't disable", i.Name, syml)
return fmt.Errorf("%s isn't managed by hub", i.Name)
}
hubpath, err := os.Readlink(syml)
if err != nil {
return fmt.Errorf("while reading symlink: %w", err)
}
absPath, err := filepath.Abs(i.hub.local.HubDir + "/" + i.RemotePath)
if err != nil {
return fmt.Errorf("while abs path: %w", err)
}
if hubpath != absPath {
log.Warningf("%s (%s) isn't a symlink to %s", i.Name, syml, absPath)
return fmt.Errorf("%s isn't managed by hub", i.Name)
}
if err = os.Remove(syml); err != nil {
if os.IsNotExist(err) {
log.Debugf("%s doesn't exist, no need to remove", syml)
return nil
}
return fmt.Errorf("while removing symlink: %w", err)
}
log.Infof("Removed symlink [%s]: %s", i.Name, syml)
}
i.Installed = false
if purge {
if err = i.purge(); err != nil {
return err
}
}
return nil
}