Refact cwhub: minor cleanups and comments (#2574)
* check response status before body; close file * err check one-liners, lint, comments * simplify function logic, reduce code * comments, xxx, whitespace
This commit is contained in:
parent
fcd6c468c4
commit
450c263826
|
@ -5,7 +5,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/agext/levenshtein"
|
"github.com/agext/levenshtein"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
|
@ -15,38 +14,26 @@ import (
|
||||||
|
|
||||||
const MaxDistance = 7
|
const MaxDistance = 7
|
||||||
|
|
||||||
func Suggest(itemType string, baseItem string, suggestItem string, score int, ignoreErr bool) {
|
// SuggestNearestMessage returns a message with the most similar item name, if one is found
|
||||||
errMsg := ""
|
func SuggestNearestMessage(hub *cwhub.Hub, itemType string, itemName string) string {
|
||||||
|
score := 100
|
||||||
|
nearest := ""
|
||||||
|
|
||||||
|
for _, item := range hub.GetItemMap(itemType) {
|
||||||
|
d := levenshtein.Distance(itemName, item.Name, nil)
|
||||||
|
if d < score {
|
||||||
|
score = d
|
||||||
|
nearest = item.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("can't find '%s' in %s", itemName, itemType)
|
||||||
|
|
||||||
if score < MaxDistance {
|
if score < MaxDistance {
|
||||||
errMsg = fmt.Sprintf("can't find '%s' in %s, did you mean %s?", baseItem, itemType, suggestItem)
|
msg += fmt.Sprintf(", did you mean '%s'?", nearest)
|
||||||
} else {
|
|
||||||
errMsg = fmt.Sprintf("can't find '%s' in %s", baseItem, itemType)
|
|
||||||
}
|
|
||||||
if ignoreErr {
|
|
||||||
log.Error(errMsg)
|
|
||||||
} else {
|
|
||||||
log.Fatalf(errMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDistance(hub *cwhub.Hub, itemType string, itemName string) (*cwhub.Item, int) {
|
|
||||||
allItems := make([]string, 0)
|
|
||||||
nearestScore := 100
|
|
||||||
nearestItem := &cwhub.Item{}
|
|
||||||
|
|
||||||
hubItems := hub.GetItemMap(itemType)
|
|
||||||
for _, item := range hubItems {
|
|
||||||
allItems = append(allItems, item.Name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range allItems {
|
return msg
|
||||||
d := levenshtein.Distance(itemName, s, nil)
|
|
||||||
if d < nearestScore {
|
|
||||||
nearestScore = d
|
|
||||||
nearestItem = hub.GetItem(itemType, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nearestItem, nearestScore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func compAllItems(itemType string, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
func compAllItems(itemType string, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
@ -56,13 +43,15 @@ func compAllItems(itemType string, args []string, toComplete string) ([]string,
|
||||||
}
|
}
|
||||||
|
|
||||||
comp := make([]string, 0)
|
comp := make([]string, 0)
|
||||||
hubItems := hub.GetItemMap(itemType)
|
|
||||||
for _, item := range hubItems {
|
for _, item := range hub.GetItemMap(itemType) {
|
||||||
if !slices.Contains(args, item.Name) && strings.Contains(item.Name, toComplete) {
|
if !slices.Contains(args, item.Name) && strings.Contains(item.Name, toComplete) {
|
||||||
comp = append(comp, item.Name)
|
comp = append(comp, item.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cobra.CompDebugln(fmt.Sprintf("%s: %+v", itemType, comp), true)
|
cobra.CompDebugln(fmt.Sprintf("%s: %+v", itemType, comp), true)
|
||||||
|
|
||||||
return comp, cobra.ShellCompDirectiveNoFileComp
|
return comp, cobra.ShellCompDirectiveNoFileComp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -203,11 +203,13 @@ func itemsInstallRunner(it hubItemType) func(cmd *cobra.Command, args []string)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range args {
|
for _, name := range args {
|
||||||
t := hub.GetItem(it.name, name)
|
if hub.GetItem(it.name, name) == nil {
|
||||||
if t == nil {
|
msg := SuggestNearestMessage(hub, it.name, name)
|
||||||
nearestItem, score := GetDistance(hub, it.name, name)
|
if !ignoreError {
|
||||||
Suggest(it.name, name, nearestItem.Name, score, ignoreError)
|
return fmt.Errorf(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Errorf(msg)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,6 +220,7 @@ func itemsInstallRunner(it hubItemType) func(cmd *cobra.Command, args []string)
|
||||||
log.Errorf("Error while installing '%s': %s", name, err)
|
log.Errorf("Error while installing '%s': %s", name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: only reload if we installed something
|
// XXX: only reload if we installed something
|
||||||
log.Infof(ReloadMessage())
|
log.Infof(ReloadMessage())
|
||||||
return nil
|
return nil
|
||||||
|
@ -281,6 +284,7 @@ func itemsRemoveRunner(it hubItemType) func(cmd *cobra.Command, args []string) e
|
||||||
}
|
}
|
||||||
|
|
||||||
removed := 0
|
removed := 0
|
||||||
|
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
didRemove, err := hub.RemoveItem(it.name, item.Name, purge, force)
|
didRemove, err := hub.RemoveItem(it.name, item.Name, purge, force)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -290,6 +294,7 @@ func itemsRemoveRunner(it hubItemType) func(cmd *cobra.Command, args []string) e
|
||||||
removed++
|
removed++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Removed %d %s", removed, it.name)
|
log.Infof("Removed %d %s", removed, it.name)
|
||||||
if removed > 0 {
|
if removed > 0 {
|
||||||
log.Infof(ReloadMessage())
|
log.Infof(ReloadMessage())
|
||||||
|
@ -478,7 +483,7 @@ func itemsInspectRunner(it hubItemType) func(cmd *cobra.Command, args []string)
|
||||||
if item == nil {
|
if item == nil {
|
||||||
return fmt.Errorf("can't find '%s' in %s", name, it.name)
|
return fmt.Errorf("can't find '%s' in %s", name, it.name)
|
||||||
}
|
}
|
||||||
if err = InspectItem(hub, item, noMetrics); err != nil {
|
if err = InspectItem(hub, item, !noMetrics); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ func selectItems(hub *cwhub.Hub, itemType string, args []string, installedOnly b
|
||||||
itemNames := hub.GetItemNames(itemType)
|
itemNames := hub.GetItemNames(itemType)
|
||||||
|
|
||||||
notExist := []string{}
|
notExist := []string{}
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
if !slices.Contains(itemNames, arg) {
|
if !slices.Contains(itemNames, arg) {
|
||||||
|
@ -98,6 +99,7 @@ func ListItems(hub *cwhub.Hub, out io.Writer, itemTypes []string, args []string,
|
||||||
out.Write(x)
|
out.Write(x)
|
||||||
case "raw":
|
case "raw":
|
||||||
csvwriter := csv.NewWriter(out)
|
csvwriter := csv.NewWriter(out)
|
||||||
|
|
||||||
if showHeader {
|
if showHeader {
|
||||||
header := []string{"name", "status", "version", "description"}
|
header := []string{"name", "status", "version", "description"}
|
||||||
if showType {
|
if showType {
|
||||||
|
@ -124,8 +126,7 @@ func ListItems(hub *cwhub.Hub, out io.Writer, itemTypes []string, args []string,
|
||||||
if showType {
|
if showType {
|
||||||
row = append(row, itemType)
|
row = append(row, itemType)
|
||||||
}
|
}
|
||||||
err := csvwriter.Write(row)
|
if err := csvwriter.Write(row); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write raw output: %s", err)
|
return fmt.Errorf("failed to write raw output: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,34 +139,26 @@ func ListItems(hub *cwhub.Hub, out io.Writer, itemTypes []string, args []string,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InspectItem(hub *cwhub.Hub, item *cwhub.Item, noMetrics bool) error {
|
func InspectItem(hub *cwhub.Hub, item *cwhub.Item, showMetrics bool) error {
|
||||||
var (
|
|
||||||
b []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
switch csConfig.Cscli.Output {
|
switch csConfig.Cscli.Output {
|
||||||
case "human", "raw":
|
case "human", "raw":
|
||||||
enc := yaml.NewEncoder(os.Stdout)
|
enc := yaml.NewEncoder(os.Stdout)
|
||||||
enc.SetIndent(2)
|
enc.SetIndent(2)
|
||||||
err = enc.Encode(item)
|
if err := enc.Encode(item); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode item: %s", err)
|
return fmt.Errorf("unable to encode item: %s", err)
|
||||||
}
|
}
|
||||||
case "json":
|
case "json":
|
||||||
b, err = json.MarshalIndent(*item, "", " ")
|
b, err := json.MarshalIndent(*item, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to marshal item: %s", err)
|
return fmt.Errorf("unable to marshal item: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("%s", string(b))
|
fmt.Print(string(b))
|
||||||
}
|
|
||||||
|
|
||||||
if noMetrics || csConfig.Cscli.Output == "json" || csConfig.Cscli.Output == "raw" {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if csConfig.Cscli.Output == "human" && showMetrics {
|
||||||
fmt.Printf("\nCurrent metrics: \n")
|
fmt.Printf("\nCurrent metrics: \n")
|
||||||
ShowMetrics(hub, item)
|
ShowMetrics(hub, item)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,3 @@
|
||||||
// This includes retrieving the index, the items to install (parsers, scenarios, data files...)
|
// This includes retrieving the index, the items to install (parsers, scenarios, data files...)
|
||||||
// and managing the dependencies and taints.
|
// and managing the dependencies and taints.
|
||||||
package cwhub
|
package cwhub
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrMissingReference = errors.New("Reference(s) missing in collection")
|
|
||||||
|
|
|
@ -32,27 +32,27 @@ func downloadFile(url string, destPath string) error {
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("got HTTP status '%s' from %s", resp.Status, url)
|
||||||
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("download response 'HTTP %d' : %s", resp.StatusCode, string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.OpenFile(destPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
|
file, err := os.OpenFile(destPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
_, err = file.Write(body)
|
_, err = file.Write(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = file.Sync()
|
if err = file.Sync(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,8 +64,7 @@ func GetData(data []*types.DataSource, dataDir string) error {
|
||||||
destPath := filepath.Join(dataDir, dataS.DestPath)
|
destPath := filepath.Join(dataDir, dataS.DestPath)
|
||||||
log.Infof("downloading data '%s' in '%s'", dataS.SourceURL, destPath)
|
log.Infof("downloading data '%s' in '%s'", dataS.SourceURL, destPath)
|
||||||
|
|
||||||
err := downloadFile(dataS.SourceURL, destPath)
|
if err := downloadFile(dataS.SourceURL, destPath); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,15 +74,12 @@ func GetData(data []*types.DataSource, dataDir string) error {
|
||||||
|
|
||||||
// downloadData downloads the data files for an item
|
// downloadData downloads the data files for an item
|
||||||
func downloadData(dataFolder string, force bool, reader io.Reader) error {
|
func downloadData(dataFolder string, force bool, reader io.Reader) error {
|
||||||
var err error
|
|
||||||
|
|
||||||
dec := yaml.NewDecoder(reader)
|
dec := yaml.NewDecoder(reader)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
data := &DataSet{}
|
data := &DataSet{}
|
||||||
|
|
||||||
err = dec.Decode(data)
|
if err := dec.Decode(data); err != nil {
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -94,14 +90,13 @@ func downloadData(dataFolder string, force bool, reader io.Reader) error {
|
||||||
download := false
|
download := false
|
||||||
|
|
||||||
for _, dataS := range data.Data {
|
for _, dataS := range data.Data {
|
||||||
if _, err = os.Stat(filepath.Join(dataFolder, dataS.DestPath)); os.IsNotExist(err) {
|
if _, err := os.Stat(filepath.Join(dataFolder, dataS.DestPath)); os.IsNotExist(err) {
|
||||||
download = true
|
download = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if download || force {
|
if download || force {
|
||||||
err = GetData(data.Data, dataFolder)
|
if err := GetData(data.Data, dataFolder); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("while getting data: %w", err)
|
return fmt.Errorf("while getting data: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,6 @@ import (
|
||||||
// creates symlink between actual config file at hub.HubDir and hub.ConfigDir
|
// creates symlink between actual config file at hub.HubDir and hub.ConfigDir
|
||||||
// Handles collections recursively
|
// Handles collections recursively
|
||||||
func (h *Hub) EnableItem(target *Item) error {
|
func (h *Hub) EnableItem(target *Item) error {
|
||||||
var err error
|
|
||||||
|
|
||||||
parentDir := filepath.Clean(h.local.InstallDir + "/" + target.Type + "/" + target.Stage + "/")
|
parentDir := filepath.Clean(h.local.InstallDir + "/" + target.Type + "/" + target.Stage + "/")
|
||||||
|
|
||||||
// create directories if needed
|
// create directories if needed
|
||||||
|
@ -35,7 +33,7 @@ func (h *Hub) EnableItem(target *Item) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = os.Stat(parentDir); os.IsNotExist(err) {
|
if _, err := os.Stat(parentDir); os.IsNotExist(err) {
|
||||||
log.Infof("%s doesn't exist, create", parentDir)
|
log.Infof("%s doesn't exist, create", parentDir)
|
||||||
|
|
||||||
if err = os.MkdirAll(parentDir, os.ModePerm); err != nil {
|
if err = os.MkdirAll(parentDir, os.ModePerm); err != nil {
|
||||||
|
@ -51,15 +49,14 @@ func (h *Hub) EnableItem(target *Item) error {
|
||||||
return fmt.Errorf("required %s %s of %s doesn't exist, abort", sub.Type, sub.Name, target.Name)
|
return fmt.Errorf("required %s %s of %s doesn't exist, abort", sub.Type, sub.Name, target.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.EnableItem(&val)
|
if err := h.EnableItem(&val); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("while installing %s: %w", sub.Name, err)
|
return fmt.Errorf("while installing %s: %w", sub.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if file already exists where it should in configdir (eg /etc/crowdsec/collections/)
|
// check if file already exists where it should in configdir (eg /etc/crowdsec/collections/)
|
||||||
if _, err = os.Lstat(parentDir + "/" + target.FileName); !os.IsNotExist(err) {
|
if _, err := os.Lstat(parentDir + "/" + target.FileName); !os.IsNotExist(err) {
|
||||||
log.Infof("%s already exists.", parentDir+"/"+target.FileName)
|
log.Infof("%s already exists.", parentDir+"/"+target.FileName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -79,7 +76,7 @@ func (h *Hub) EnableItem(target *Item) error {
|
||||||
return fmt.Errorf("while creating symlink from %s to %s: %w", srcPath, dstPath, err)
|
return fmt.Errorf("while creating symlink from %s to %s: %w", srcPath, dstPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Enabled %s : %s", target.Type, target.Name)
|
log.Infof("Enabled %s: %s", target.Type, target.Name)
|
||||||
target.Installed = true
|
target.Installed = true
|
||||||
h.Items[target.Type][target.Name] = *target
|
h.Items[target.Type][target.Name] = *target
|
||||||
|
|
||||||
|
@ -103,6 +100,7 @@ func (h *Hub) purgeItem(target Item) (Item, error) {
|
||||||
|
|
||||||
// DisableItem to disable an item managed by the hub, removes the symlink if purge is true
|
// DisableItem to disable an item managed by the hub, removes the symlink if purge is true
|
||||||
func (h *Hub) DisableItem(target *Item, purge bool, force bool) error {
|
func (h *Hub) DisableItem(target *Item, 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
|
var err error
|
||||||
|
|
||||||
// already disabled, noop unless purge
|
// already disabled, noop unless purge
|
||||||
|
@ -145,8 +143,7 @@ func (h *Hub) DisableItem(target *Item, purge bool, force bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if toRemove {
|
if toRemove {
|
||||||
err = h.DisableItem(&val, purge, force)
|
if err = h.DisableItem(&val, purge, force); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("while disabling %s: %w", sub.Name, err)
|
return fmt.Errorf("while disabling %s: %w", sub.Name, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -164,7 +161,7 @@ func (h *Hub) DisableItem(target *Item, purge bool, force bool) error {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
// we only accept to "delete" non existing items if it's a forced purge
|
// we only accept to "delete" non existing items if it's a forced purge
|
||||||
if !purge && !force {
|
if !purge && !force {
|
||||||
return fmt.Errorf("can't delete %s : %s doesn't exist", target.Name, syml)
|
return fmt.Errorf("can't delete %s: %s doesn't exist", target.Name, syml)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if it's managed by hub, it's a symlink to csconfig.GConfig.hub.HubDir / ...
|
// if it's managed by hub, it's a symlink to csconfig.GConfig.hub.HubDir / ...
|
||||||
|
@ -193,7 +190,7 @@ func (h *Hub) DisableItem(target *Item, purge bool, force bool) error {
|
||||||
return fmt.Errorf("while removing symlink: %w", err)
|
return fmt.Errorf("while removing symlink: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Removed symlink [%s] : %s", target.Name, syml)
|
log.Infof("Removed symlink [%s]: %s", target.Name, syml)
|
||||||
}
|
}
|
||||||
|
|
||||||
target.Installed = false
|
target.Installed = false
|
||||||
|
|
|
@ -72,7 +72,7 @@ func testDisable(hub *Hub, t *testing.T, item Item) {
|
||||||
// Local sync and check status
|
// Local sync and check status
|
||||||
warns, err := hub.LocalSync()
|
warns, err := hub.LocalSync()
|
||||||
require.NoError(t, err, "failed to run localSync")
|
require.NoError(t, err, "failed to run localSync")
|
||||||
require.Empty(t, warns, "unexpected warnings : %+v", warns)
|
require.Empty(t, warns, "unexpected warnings: %+v", warns)
|
||||||
|
|
||||||
assert.False(t, hub.Items[item.Type][item.Name].Tainted, "%s should not be tainted anymore", item.Name)
|
assert.False(t, hub.Items[item.Type][item.Name].Tainted, "%s should not be tainted anymore", item.Name)
|
||||||
assert.False(t, hub.Items[item.Type][item.Name].Installed, "%s should not be installed anymore", item.Name)
|
assert.False(t, hub.Items[item.Type][item.Name].Installed, "%s should not be installed anymore", item.Name)
|
||||||
|
@ -85,7 +85,7 @@ func testDisable(hub *Hub, t *testing.T, item Item) {
|
||||||
// Local sync and check status
|
// Local sync and check status
|
||||||
warns, err = hub.LocalSync()
|
warns, err = hub.LocalSync()
|
||||||
require.NoError(t, err, "failed to run localSync")
|
require.NoError(t, err, "failed to run localSync")
|
||||||
require.Empty(t, warns, "unexpected warnings : %+v", warns)
|
require.Empty(t, warns, "unexpected warnings: %+v", warns)
|
||||||
|
|
||||||
assert.False(t, hub.Items[item.Type][item.Name].Installed, "%s should not be installed anymore", item.Name)
|
assert.False(t, hub.Items[item.Type][item.Name].Installed, "%s should not be installed anymore", item.Name)
|
||||||
assert.False(t, hub.Items[item.Type][item.Name].Downloaded, "%s should not be downloaded", item.Name)
|
assert.False(t, hub.Items[item.Type][item.Name].Downloaded, "%s should not be downloaded", item.Name)
|
||||||
|
|
12
pkg/cwhub/errors.go
Normal file
12
pkg/cwhub/errors.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package cwhub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNilRemoteHub is returned when the remote hub configuration is not provided to the NewHub constructor.
|
||||||
|
// All attempts to download index or items will return this error.
|
||||||
|
ErrMissingReference = errors.New("Reference(s) missing in collection")
|
||||||
|
ErrNilRemoteHub = errors.New("remote hub configuration is not provided. Please report this issue to the developers")
|
||||||
|
)
|
|
@ -34,22 +34,24 @@ func (h *Hub) InstallItem(name string, itemType string, force bool, downloadOnly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.DownloadLatest(item, force, true)
|
// XXX: confusing semantic between force and updateOnly?
|
||||||
if err != nil {
|
if err := h.DownloadLatest(item, force, true); err != nil {
|
||||||
return fmt.Errorf("while downloading %s: %w", item.Name, err)
|
return fmt.Errorf("while downloading %s: %w", item.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.AddItem(*item); err != nil {
|
if err := h.AddItem(*item); err != nil {
|
||||||
return fmt.Errorf("while adding %s: %w", item.Name, err)
|
return fmt.Errorf("while adding %s: %w", item.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if downloadOnly {
|
if downloadOnly {
|
||||||
|
// XXX: should get the path from DownloadLatest
|
||||||
log.Infof("Downloaded %s to %s", item.Name, filepath.Join(h.local.HubDir, item.RemotePath))
|
log.Infof("Downloaded %s to %s", item.Name, filepath.Join(h.local.HubDir, item.RemotePath))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.EnableItem(item)
|
// XXX: should we stop here if the item is already installed?
|
||||||
if err != nil {
|
|
||||||
|
if err := h.EnableItem(item); err != nil {
|
||||||
return fmt.Errorf("while enabling %s: %w", item.Name, err)
|
return fmt.Errorf("while enabling %s: %w", item.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,13 +155,12 @@ func (h *Hub) UpgradeItem(itemType string, name string, force bool) (bool, error
|
||||||
|
|
||||||
// DownloadLatest will download the latest version of Item to the tdir directory
|
// DownloadLatest will download the latest version of Item to the tdir directory
|
||||||
func (h *Hub) DownloadLatest(target *Item, overwrite bool, updateOnly bool) error {
|
func (h *Hub) DownloadLatest(target *Item, overwrite bool, updateOnly bool) error {
|
||||||
var err error
|
// XXX: should return the path of the downloaded file (taken from DownloadItem)
|
||||||
|
|
||||||
log.Debugf("Downloading %s %s", target.Type, target.Name)
|
log.Debugf("Downloading %s %s", target.Type, target.Name)
|
||||||
|
|
||||||
if target.Type != COLLECTIONS {
|
if target.Type != COLLECTIONS {
|
||||||
if !target.Installed && updateOnly && target.Downloaded {
|
if !target.Installed && updateOnly && target.Downloaded {
|
||||||
log.Debugf("skipping upgrade of %s : not installed", target.Name)
|
log.Debugf("skipping upgrade of %s: not installed", target.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,32 +175,31 @@ func (h *Hub) DownloadLatest(target *Item, overwrite bool, updateOnly bool) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
if !val.Installed && updateOnly && val.Downloaded {
|
if !val.Installed && updateOnly && val.Downloaded {
|
||||||
log.Debugf("skipping upgrade of %s : not installed", target.Name)
|
log.Debugf("skipping upgrade of %s: not installed", target.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Download %s sub-item : %s %s (%t -> %t)", target.Name, sub.Type, sub.Name, target.Installed, updateOnly)
|
log.Debugf("Download %s sub-item: %s %s (%t -> %t)", target.Name, sub.Type, sub.Name, target.Installed, updateOnly)
|
||||||
//recurse as it's a collection
|
|
||||||
|
// recurse as it's a collection
|
||||||
if sub.Type == COLLECTIONS {
|
if sub.Type == COLLECTIONS {
|
||||||
log.Tracef("collection, recurse")
|
log.Tracef("collection, recurse")
|
||||||
|
|
||||||
err = h.DownloadLatest(&val, overwrite, updateOnly)
|
if err := h.DownloadLatest(&val, overwrite, updateOnly); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("while downloading %s: %w", val.Name, err)
|
return fmt.Errorf("while downloading %s: %w", val.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
downloaded := val.Downloaded
|
downloaded := val.Downloaded
|
||||||
|
|
||||||
err = h.DownloadItem(&val, overwrite)
|
if err := h.DownloadItem(&val, overwrite); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("while downloading %s: %w", val.Name, err)
|
return fmt.Errorf("while downloading %s: %w", val.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to enable an item when it has been added to a collection since latest release of the collection.
|
// We need to enable an item when it has been added to a collection since latest release of the collection.
|
||||||
// We check if val.Downloaded is false because maybe the item has been disabled by the user.
|
// We check if val.Downloaded is false because maybe the item has been disabled by the user.
|
||||||
if !val.Installed && !downloaded {
|
if !val.Installed && !downloaded {
|
||||||
if err = h.EnableItem(&val); err != nil {
|
if err := h.EnableItem(&val); err != nil {
|
||||||
return fmt.Errorf("enabling '%s': %w", val.Name, err)
|
return fmt.Errorf("enabling '%s': %w", val.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,8 +207,7 @@ func (h *Hub) DownloadLatest(target *Item, overwrite bool, updateOnly bool) erro
|
||||||
h.Items[sub.Type][sub.Name] = val
|
h.Items[sub.Type][sub.Name] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.DownloadItem(target, overwrite)
|
if err := h.DownloadItem(target, overwrite); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to download item: %w", err)
|
return fmt.Errorf("failed to download item: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,13 +225,13 @@ func (h *Hub) DownloadItem(target *Item, overwrite bool) error {
|
||||||
// if user didn't --force, don't overwrite local, tainted, up-to-date files
|
// if user didn't --force, don't overwrite local, tainted, up-to-date files
|
||||||
if !overwrite {
|
if !overwrite {
|
||||||
if target.Tainted {
|
if target.Tainted {
|
||||||
log.Debugf("%s : tainted, not updated", target.Name)
|
log.Debugf("%s: tainted, not updated", target.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if target.UpToDate {
|
if target.UpToDate {
|
||||||
// We still have to check if data files are present
|
// We still have to check if data files are present
|
||||||
log.Debugf("%s : up-to-date, not updated", target.Name)
|
log.Debugf("%s: up-to-date, not updated", target.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,13 +244,12 @@ func (h *Hub) DownloadItem(target *Item, overwrite bool) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("while downloading %s: %w", req.URL.String(), err)
|
return fmt.Errorf("while downloading %s: %w", req.URL.String(), err)
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("bad http code %d for %s", resp.StatusCode, req.URL.String())
|
return fmt.Errorf("bad http code %d for %s", resp.StatusCode, req.URL.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("while reading %s: %w", req.URL.String(), err)
|
return fmt.Errorf("while reading %s: %w", req.URL.String(), err)
|
||||||
|
@ -296,10 +294,10 @@ func (h *Hub) DownloadItem(target *Item, overwrite bool) error {
|
||||||
|
|
||||||
// check actual file
|
// check actual file
|
||||||
if _, err = os.Stat(finalPath); !os.IsNotExist(err) {
|
if _, err = os.Stat(finalPath); !os.IsNotExist(err) {
|
||||||
log.Warningf("%s : overwrite", target.Name)
|
log.Warningf("%s: overwrite", target.Name)
|
||||||
log.Debugf("target: %s/%s", tdir, target.RemotePath)
|
log.Debugf("target: %s/%s", tdir, target.RemotePath)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("%s : OK", target.Name)
|
log.Infof("%s: OK", target.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.OpenFile(tdir+"/"+target.RemotePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
|
f, err := os.OpenFile(tdir+"/"+target.RemotePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
|
||||||
|
|
|
@ -44,8 +44,7 @@ func NewHub(local *csconfig.LocalHubCfg, remote *RemoteHubCfg, downloadIndex boo
|
||||||
}
|
}
|
||||||
|
|
||||||
if downloadIndex {
|
if downloadIndex {
|
||||||
err := remote.DownloadIndex(local.HubIndexFile)
|
if err := remote.DownloadIndex(local.HubIndexFile); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,8 +72,7 @@ func NewHub(local *csconfig.LocalHubCfg, remote *RemoteHubCfg, downloadIndex boo
|
||||||
remote: remote,
|
remote: remote,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = theHub.LocalSync()
|
if _, err = theHub.LocalSync(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to sync hub index: %w", err)
|
return nil, fmt.Errorf("failed to sync hub index: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,7 @@ func (i *Item) IsLocal() bool {
|
||||||
// It must not use a pointer receiver
|
// It must not use a pointer receiver
|
||||||
func (i Item) MarshalJSON() ([]byte, error) {
|
func (i Item) MarshalJSON() ([]byte, error) {
|
||||||
type Alias Item
|
type Alias Item
|
||||||
|
|
||||||
return json.Marshal(&struct {
|
return json.Marshal(&struct {
|
||||||
Alias
|
Alias
|
||||||
Local bool `json:"local"` // XXX: omitempty?
|
Local bool `json:"local"` // XXX: omitempty?
|
||||||
|
@ -90,6 +91,7 @@ func (i Item) MarshalJSON() ([]byte, error) {
|
||||||
// It must not use a pointer receiver
|
// It must not use a pointer receiver
|
||||||
func (i Item) MarshalYAML() (interface{}, error) {
|
func (i Item) MarshalYAML() (interface{}, error) {
|
||||||
type Alias Item
|
type Alias Item
|
||||||
|
|
||||||
return &struct {
|
return &struct {
|
||||||
Alias `yaml:",inline"`
|
Alias `yaml:",inline"`
|
||||||
Local bool `yaml:"local"`
|
Local bool `yaml:"local"`
|
||||||
|
|
|
@ -15,7 +15,7 @@ func TestItemStatus(t *testing.T) {
|
||||||
x := hub.GetItemMap(COLLECTIONS)
|
x := hub.GetItemMap(COLLECTIONS)
|
||||||
require.NotEmpty(t, x)
|
require.NotEmpty(t, x)
|
||||||
|
|
||||||
// Get item : good and bad
|
// Get item: good and bad
|
||||||
for k := range x {
|
for k := range x {
|
||||||
item := hub.GetItem(COLLECTIONS, k)
|
item := hub.GetItem(COLLECTIONS, k)
|
||||||
require.NotNil(t, item)
|
require.NotNil(t, item)
|
||||||
|
@ -52,7 +52,7 @@ func TestGetters(t *testing.T) {
|
||||||
x := hub.GetItemMap(COLLECTIONS)
|
x := hub.GetItemMap(COLLECTIONS)
|
||||||
require.NotEmpty(t, x)
|
require.NotEmpty(t, x)
|
||||||
|
|
||||||
// Get item : good and bad
|
// Get item: good and bad
|
||||||
for k := range x {
|
for k := range x {
|
||||||
empty := hub.GetItem(COLLECTIONS, k+"nope")
|
empty := hub.GetItem(COLLECTIONS, k+"nope")
|
||||||
require.Nil(t, empty)
|
require.Nil(t, empty)
|
||||||
|
|
|
@ -17,9 +17,6 @@ type RemoteHubCfg struct {
|
||||||
IndexPath string
|
IndexPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrNilRemoteHub is returned when the remote hub configuration is not provided to the NewHub constructor. All attempts to download index or items will return this error.
|
|
||||||
var ErrNilRemoteHub = fmt.Errorf("remote hub configuration is not provided. Please report this issue to the developers")
|
|
||||||
|
|
||||||
func (r *RemoteHubCfg) urlTo(remotePath string) (string, error) {
|
func (r *RemoteHubCfg) urlTo(remotePath string) (string, error) {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return "", ErrNilRemoteHub
|
return "", ErrNilRemoteHub
|
||||||
|
@ -59,7 +56,7 @@ func (r *RemoteHubCfg) DownloadIndex(localPath string) error {
|
||||||
return ErrIndexNotFound
|
return ErrIndexNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("bad http code %d while requesting %s", resp.StatusCode, req.URL.String())
|
return fmt.Errorf("bad http code %d for %s", resp.StatusCode, req.URL.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
|
|
@ -81,7 +81,7 @@ func (h *Hub) getItemInfo(path string) (itemFileInfo, bool, error) {
|
||||||
//.../hub/scenarios/crowdsec/ssh_bf.yaml
|
//.../hub/scenarios/crowdsec/ssh_bf.yaml
|
||||||
//.../hub/profiles/crowdsec/linux.yaml
|
//.../hub/profiles/crowdsec/linux.yaml
|
||||||
if len(subs) < 4 {
|
if len(subs) < 4 {
|
||||||
return itemFileInfo{}, false, fmt.Errorf("path is too short : %s (%d)", path, len(subs))
|
return itemFileInfo{}, false, fmt.Errorf("path is too short: %s (%d)", path, len(subs))
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.fname = subs[len(subs)-1]
|
ret.fname = subs[len(subs)-1]
|
||||||
|
@ -115,7 +115,7 @@ func (h *Hub) getItemInfo(path string) (itemFileInfo, bool, error) {
|
||||||
ret.ftype = COLLECTIONS
|
ret.ftype = COLLECTIONS
|
||||||
ret.stage = ""
|
ret.stage = ""
|
||||||
} else if ret.ftype != PARSERS && ret.ftype != POSTOVERFLOWS {
|
} else if ret.ftype != PARSERS && ret.ftype != POSTOVERFLOWS {
|
||||||
// its a PARSER / POSTOVERFLOW with a stage
|
// it's a PARSER / POSTOVERFLOW with a stage
|
||||||
return itemFileInfo{}, inhub, fmt.Errorf("unknown configuration type for file '%s'", path)
|
return itemFileInfo{}, inhub, fmt.Errorf("unknown configuration type for file '%s'", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ func (h *Hub) itemVisit(path string, f os.DirEntry, err error) error {
|
||||||
match := false
|
match := false
|
||||||
|
|
||||||
for name, item := range h.Items[info.ftype] {
|
for name, item := range h.Items[info.ftype] {
|
||||||
log.Tracef("check [%s] vs [%s] : %s", info.fname, item.RemotePath, info.ftype+"/"+info.stage+"/"+info.fname+".yaml")
|
log.Tracef("check [%s] vs [%s]: %s", info.fname, item.RemotePath, info.ftype+"/"+info.stage+"/"+info.fname+".yaml")
|
||||||
|
|
||||||
if info.fname != item.FileName {
|
if info.fname != item.FileName {
|
||||||
log.Tracef("%s != %s (filename)", info.fname, item.FileName)
|
log.Tracef("%s != %s (filename)", info.fname, item.FileName)
|
||||||
|
@ -241,10 +241,11 @@ func (h *Hub) itemVisit(path string, f os.DirEntry, err error) error {
|
||||||
|
|
||||||
sha, err := getSHA256(path)
|
sha, err := getSHA256(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to get sha of %s : %v", path, err)
|
log.Fatalf("Failed to get sha of %s: %v", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// let's reverse sort the versions to deal with hash collisions (#154)
|
// let's reverse sort the versions to deal with hash collisions (#154)
|
||||||
|
// XXX: we sure, lexical sorting?
|
||||||
versions := make([]string, 0, len(item.Versions))
|
versions := make([]string, 0, len(item.Versions))
|
||||||
for k := range item.Versions {
|
for k := range item.Versions {
|
||||||
versions = append(versions, k)
|
versions = append(versions, k)
|
||||||
|
@ -253,8 +254,7 @@ func (h *Hub) itemVisit(path string, f os.DirEntry, err error) error {
|
||||||
sort.Sort(sort.Reverse(sort.StringSlice(versions)))
|
sort.Sort(sort.Reverse(sort.StringSlice(versions)))
|
||||||
|
|
||||||
for _, version := range versions {
|
for _, version := range versions {
|
||||||
val := item.Versions[version]
|
if item.Versions[version].Digest != sha {
|
||||||
if sha != val.Digest {
|
|
||||||
// log.Infof("matching filenames, wrong hash %s != %s -- %s", sha, val.Digest, spew.Sdump(v))
|
// log.Infof("matching filenames, wrong hash %s != %s -- %s", sha, val.Digest, spew.Sdump(v))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -287,6 +287,7 @@ func (h *Hub) itemVisit(path string, f os.DirEntry, err error) error {
|
||||||
log.Tracef("got tainted match for %s: %s", item.Name, path)
|
log.Tracef("got tainted match for %s: %s", item.Name, path)
|
||||||
|
|
||||||
h.skippedTainted++
|
h.skippedTainted++
|
||||||
|
|
||||||
// the file and the stage is right, but the hash is wrong, it has been tainted by user
|
// the file and the stage is right, but the hash is wrong, it has been tainted by user
|
||||||
if !inhub {
|
if !inhub {
|
||||||
item.LocalPath = path
|
item.LocalPath = path
|
||||||
|
@ -400,8 +401,7 @@ func (h *Hub) SyncDir(dir string) ([]string, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = filepath.WalkDir(cpath, h.itemVisit)
|
if err = filepath.WalkDir(cpath, h.itemVisit); err != nil {
|
||||||
if err != nil {
|
|
||||||
return warnings, err
|
return warnings, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -424,7 +424,7 @@ func (h *Hub) SyncDir(dir string) ([]string, error) {
|
||||||
warnings = append(warnings, fmt.Sprintf("collection %s is in the future (currently:%s, latest:%s)", item.Name, item.LocalVersion, item.Version))
|
warnings = append(warnings, fmt.Sprintf("collection %s is in the future (currently:%s, latest:%s)", item.Name, item.LocalVersion, item.Version))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("installed (%s) - status:%d | installed:%s | latest : %s | full : %+v", item.Name, vs, item.LocalVersion, item.Version, item.Versions)
|
log.Debugf("installed (%s) - status: %d | installed: %s | latest: %s | full: %+v", item.Name, vs, item.LocalVersion, item.Version, item.Versions)
|
||||||
}
|
}
|
||||||
|
|
||||||
return warnings, nil
|
return warnings, nil
|
||||||
|
@ -440,8 +440,7 @@ func (h *Hub) LocalSync() ([]string, error) {
|
||||||
return warnings, fmt.Errorf("failed to scan %s: %w", h.local.InstallDir, err)
|
return warnings, fmt.Errorf("failed to scan %s: %w", h.local.InstallDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = h.SyncDir(h.local.HubDir)
|
if _, err = h.SyncDir(h.local.HubDir); err != nil {
|
||||||
if err != nil {
|
|
||||||
return warnings, fmt.Errorf("failed to scan %s: %w", h.local.HubDir, err)
|
return warnings, fmt.Errorf("failed to scan %s: %w", h.local.HubDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue