cscli hub list: show only non-empty tables with -o human

* agent config: remove unused LintOnly bool
* Item.IsLocal() -> Item.State.IsLocal(); split method InstallStatus()
* cscli hub list: show only non-empty tables with -o human
This commit is contained in:
mmetc 2023-12-05 13:38:52 +01:00 committed by GitHub
parent 486f96e7ac
commit 1ab4487b65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 77 additions and 77 deletions

View file

@ -46,7 +46,7 @@ func backupHub(dirPath string) error {
}
//for the local/tainted ones, we back up the full file
if v.State.Tainted || v.IsLocal() || !v.State.UpToDate {
if v.State.Tainted || v.State.IsLocal() || !v.State.UpToDate {
//we need to backup stages for parsers
if itemType == cwhub.PARSERS || itemType == cwhub.POSTOVERFLOWS {
fstagedir := fmt.Sprintf("%s%s", itemDirectory, v.Stage)
@ -54,7 +54,7 @@ func backupHub(dirPath string) error {
return fmt.Errorf("error while creating stage dir %s : %s", fstagedir, err)
}
}
clog.Debugf("[%s]: backing up file (tainted:%t local:%t up-to-date:%t)", k, v.State.Tainted, v.IsLocal(), v.State.UpToDate)
clog.Debugf("[%s]: backing up file (tainted:%t local:%t up-to-date:%t)", k, v.State.Tainted, v.State.IsLocal(), v.State.UpToDate)
tfile := fmt.Sprintf("%s%s/%s", itemDirectory, v.Stage, v.FileName)
if err = CopyFile(v.State.LocalPath, tfile); err != nil {
return fmt.Errorf("failed copy %s %s to %s : %s", itemType, v.State.LocalPath, tfile, err)

View file

@ -66,7 +66,7 @@ func runHubList(cmd *cobra.Command, args []string) error {
}
}
err = listItems(color.Output, cwhub.ItemTypes, items)
err = listItems(color.Output, cwhub.ItemTypes, items, true)
if err != nil {
return err
}

View file

@ -580,7 +580,7 @@ func itemsListRunner(it hubItemType) func(cmd *cobra.Command, args []string) err
return err
}
if err = listItems(color.Output, []string{it.name}, items); err != nil {
if err = listItems(color.Output, []string{it.name}, items, false); err != nil {
return err
}

View file

@ -53,11 +53,19 @@ func selectItems(hub *cwhub.Hub, itemType string, args []string, installedOnly b
return items, nil
}
func listItems(out io.Writer, itemTypes []string, items map[string][]*cwhub.Item) error {
func listItems(out io.Writer, itemTypes []string, items map[string][]*cwhub.Item, omitIfEmpty bool) error {
switch csConfig.Cscli.Output {
case "human":
nothingToDisplay := true
for _, itemType := range itemTypes {
if omitIfEmpty && len(items[itemType]) == 0 {
continue
}
listHubItemTable(out, "\n"+strings.ToUpper(itemType), items[itemType])
nothingToDisplay = false
}
if nothingToDisplay {
fmt.Println("No items to display")
}
case "json":
type itemHubStatus struct {
@ -75,14 +83,15 @@ func listItems(out io.Writer, itemTypes []string, items map[string][]*cwhub.Item
hubStatus[itemType] = make([]itemHubStatus, len(items[itemType]))
for i, item := range items[itemType] {
status, emo := item.InstallStatus()
status := item.State.Text()
status_emo := item.State.Emoji()
hubStatus[itemType][i] = itemHubStatus{
Name: item.Name,
LocalVersion: item.State.LocalVersion,
LocalPath: item.State.LocalPath,
Description: item.Description,
Status: status,
UTF8Status: fmt.Sprintf("%v %s", emo, status),
UTF8Status: fmt.Sprintf("%v %s", status_emo, status),
}
}
}
@ -107,10 +116,9 @@ func listItems(out io.Writer, itemTypes []string, items map[string][]*cwhub.Item
for _, itemType := range itemTypes {
for _, item := range items[itemType] {
status, _ := item.InstallStatus()
row := []string{
item.Name,
status,
item.State.Text(),
item.State.LocalVersion,
item.Description,
}

View file

@ -140,7 +140,7 @@ func collectHubItems(hub *cwhub.Hub, itemType string) []byte {
log.Warnf("could not collect %s list: %s", itemType, err)
}
if err := listItems(out, []string{itemType}, items); err != nil {
if err := listItems(out, []string{itemType}, items, false); err != nil {
log.Warnf("could not collect %s list: %s", itemType, err)
}
return out.Bytes()

View file

@ -18,8 +18,8 @@ func listHubItemTable(out io.Writer, title string, items []*cwhub.Item) {
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
for _, item := range items {
status, emo := item.InstallStatus()
t.AddRow(item.Name, fmt.Sprintf("%v %s", emo, status), item.State.LocalVersion, item.State.LocalPath)
status := fmt.Sprintf("%v %s", item.State.Emoji(), item.State.Text())
t.AddRow(item.Name, status, item.State.LocalVersion, item.State.LocalPath)
}
renderTableTitle(out, title)
t.Render()

View file

@ -262,10 +262,6 @@ func LoadConfig(configFile string, disableAgent bool, disableAPI bool, quiet boo
return nil, errors.New("You must run at least the API Server or crowdsec")
}
if flags.TestMode && !cConfig.DisableAgent {
cConfig.Crowdsec.LintOnly = true
}
if flags.OneShotDSN != "" && flags.SingleFileType == "" {
return nil, errors.New("-dsn requires a -type argument")
}

View file

@ -23,7 +23,6 @@ type CrowdsecServiceCfg struct {
BucketsRoutinesCount int `yaml:"buckets_routines"`
OutputRoutinesCount int `yaml:"output_routines"`
SimulationConfig *SimulationConfig `yaml:"-"`
LintOnly bool `yaml:"-"` // if set to true, exit after loading configs
BucketStateFile string `yaml:"state_input_file,omitempty"` // if we need to unserialize buckets at start
BucketStateDumpDir string `yaml:"state_output_dir,omitempty"` // if we need to unserialize buckets on shutdown
BucketsGCEnabled bool `yaml:"-"` // we need to garbage collect buckets when in forensic mode

View file

@ -109,7 +109,7 @@ func (h *Hub) ItemStats() []string {
loaded += fmt.Sprintf("%d %s, ", len(h.GetItemMap(itemType)), itemType)
for _, item := range h.GetItemMap(itemType) {
if item.IsLocal() {
if item.State.IsLocal() {
local++
}

View file

@ -53,6 +53,48 @@ type ItemState struct {
BelongsToCollections []string `json:"belongs_to_collections,omitempty" yaml:"belongs_to_collections,omitempty"`
}
// IsLocal returns true if the item has been create by a user (not downloaded from the hub).
func (s *ItemState) IsLocal() bool {
return s.Installed && !s.Downloaded
}
// Text returns the status of the item as a string (eg. "enabled,update-available").
func (s *ItemState) Text() string {
ret := "disabled"
if s.Installed {
ret = "enabled"
}
if s.IsLocal() {
ret += ",local"
}
if s.Tainted {
ret += ",tainted"
} else if !s.UpToDate && !s.IsLocal() {
ret += ",update-available"
}
return ret
}
// Emoji returns the status of the item as an emoji (eg. emoji.Warning).
func (s *ItemState) Emoji() emoji.Emoji {
switch {
case s.IsLocal():
return emoji.House
case !s.Installed:
return emoji.Prohibited
case s.Tainted || (!s.UpToDate && !s.IsLocal()):
return emoji.Warning
case s.Installed:
return emoji.CheckMark
default:
return emoji.QuestionMark
}
}
// Item is created from an index file and enriched with local info.
type Item struct {
hub *Hub // back pointer to the hub, to retrieve other items and call install/remove methods
@ -107,11 +149,6 @@ func (i *Item) HasSubItems() bool {
return i.Type == COLLECTIONS
}
// IsLocal returns true if the item has been create by a user (not downloaded from the hub).
func (i *Item) IsLocal() bool {
return i.State.Installed && !i.State.Downloaded
}
// MarshalJSON is used to prepare the output for "cscli ... inspect -o json".
// It must not use a pointer receiver.
func (i Item) MarshalJSON() ([]byte, error) {
@ -139,7 +176,7 @@ func (i Item) MarshalJSON() ([]byte, error) {
UpToDate: i.State.UpToDate,
Tainted: i.State.Tainted,
BelongsToCollections: i.State.BelongsToCollections,
Local: i.IsLocal(),
Local: i.State.IsLocal(),
})
}
@ -155,7 +192,7 @@ func (i Item) MarshalYAML() (interface{}, error) {
}{
Alias: Alias(i),
State: i.State,
Local: i.IsLocal(),
Local: i.State.IsLocal(),
}, nil
}
@ -290,48 +327,6 @@ func (i *Item) descendants() ([]*Item, error) {
return ret, nil
}
// InstallStatus returns the status of the item as a string and an emoji
// (eg. "enabled,update-available" and emoji.Warning).
func (i *Item) InstallStatus() (string, emoji.Emoji) {
status := "disabled"
ok := false
if i.State.Installed {
ok = true
status = "enabled"
}
managed := true
if i.IsLocal() {
managed = false
status += ",local"
}
warning := false
if i.State.Tainted {
warning = true
status += ",tainted"
} else if !i.State.UpToDate && !i.IsLocal() {
warning = true
status += ",update-available"
}
emo := emoji.QuestionMark
switch {
case !managed:
emo = emoji.House
case !i.State.Installed:
emo = emoji.Prohibited
case warning:
emo = emoji.Warning
case ok:
emo = emoji.CheckMark
}
return status, emo
}
// versionStatus returns the status of the item version compared to the hub version.
// semver requires the 'v' prefix.
func (i *Item) versionStatus() int {

View file

@ -23,7 +23,7 @@ func TestItemStatus(t *testing.T) {
item.State.Tainted = false
item.State.Downloaded = true
txt, _ := item.InstallStatus()
txt := item.State.Text()
require.Equal(t, "enabled,update-available", txt)
item.State.Installed = true
@ -31,7 +31,7 @@ func TestItemStatus(t *testing.T) {
item.State.Tainted = false
item.State.Downloaded = false
txt, _ = item.InstallStatus()
txt = item.State.Text()
require.Equal(t, "enabled,local", txt)
}

View file

@ -13,7 +13,7 @@ func (i *Item) enable() error {
return fmt.Errorf("%s is tainted, won't enable unless --force", i.Name)
}
if i.IsLocal() {
if i.State.IsLocal() {
return fmt.Errorf("%s is local, won't enable", i.Name)
}

View file

@ -66,7 +66,7 @@ func (i *Item) disable(purge bool, force bool) (bool, error) {
// Remove disables the item, optionally removing the downloaded content.
func (i *Item) Remove(purge bool, force bool) (bool, error) {
if i.IsLocal() {
if i.State.IsLocal() {
log.Warningf("%s is a local item, please delete manually", i.Name)
return false, nil
}

View file

@ -20,7 +20,7 @@ import (
func (i *Item) Upgrade(force bool) (bool, error) {
updated := false
if i.IsLocal() {
if i.State.IsLocal() {
log.Infof("not upgrading %s: local item", i.Name)
return false, nil
}
@ -155,7 +155,7 @@ func (i *Item) fetch() ([]byte, error) {
// download downloads the item from the hub and writes it to the hub directory.
func (i *Item) download(overwrite bool) (string, error) {
if i.IsLocal() {
if i.State.IsLocal() {
return "", fmt.Errorf("%s is local, can't download", i.Name)
}
// if user didn't --force, don't overwrite local, tainted, up-to-date files

View file

@ -420,7 +420,7 @@ func (h *Hub) localSync() error {
case versionFuture:
warnings = append(warnings, fmt.Sprintf("collection %s is in the future (currently:%s, latest:%s)", item.Name, item.State.LocalVersion, item.Version))
case versionUnknown:
if !item.IsLocal() {
if !item.State.IsLocal() {
warnings = append(warnings, fmt.Sprintf("collection %s is tainted (latest:%s)", item.Name, item.Version))
}
}

View file

@ -34,17 +34,19 @@ teardown() {
# no items
rune -0 cscli hub list
assert_output --regexp ".*PARSERS.*POSTOVERFLOWS.*SCENARIOS.*COLLECTIONS.*"
assert_output "No items to display"
rune -0 cscli hub list -o json
assert_json '{parsers:[],scenarios:[],collections:[],postoverflows:[]}'
rune -0 cscli hub list -o raw
assert_output 'name,status,version,description,type'
# some items
# some items: with output=human, show only non-empty tables
rune -0 cscli parsers install crowdsecurity/whitelists
rune -0 cscli scenarios install crowdsecurity/telnet-bf
rune -0 cscli hub list
assert_output --regexp ".*PARSERS.*crowdsecurity/whitelists.*POSTOVERFLOWS.*SCENARIOS.*crowdsecurity/telnet-bf.*COLLECTIONS.*"
assert_output --regexp ".*PARSERS.*crowdsecurity/whitelists.*SCENARIOS.*crowdsecurity/telnet-bf.*"
refute_output --partial 'POSTOVERFLOWS'
refute_output --partial 'COLLECTIONS'
rune -0 cscli hub list -o json
rune -0 jq -e '(.parsers | length == 1) and (.scenarios | length == 1)' <(output)
rune -0 cscli hub list -o raw