Skip to content

Commit

Permalink
feat(bundle): optionally displays labels associated to a bundle
Browse files Browse the repository at this point in the history
* fixes #235

Signed-off-by: Frederic BIDON <frederic@oneconcern.com>
  • Loading branch information
fredbi committed Jan 24, 2020
1 parent b033458 commit 16ae3c3
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 26 deletions.
36 changes: 30 additions & 6 deletions cmd/datamon/cmd/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"context"
"fmt"
"log"
"strings"
"text/template"

context2 "github.com/oneconcern/datamon/pkg/context"
"github.com/oneconcern/datamon/pkg/model"

"github.com/oneconcern/datamon/pkg/core"
"github.com/spf13/cobra"
Expand All @@ -30,15 +32,22 @@ together.`,
},
}

var bundleDescriptorTemplate *template.Template

func init() {
rootCmd.AddCommand(bundleCmd)
}

bundleDescriptorTemplate = func() *template.Template {
const listLineTemplateString = `{{.ID}} , {{.Timestamp}} , {{.Message}}`
return template.Must(template.New("list line").Parse(listLineTemplateString))
}()
func bundleDescriptorTemplate(withLabels bool) *template.Template {
// bundle rendering comes in 2 flavors: one without the labels field, the other with the
// comma separated list of labels set on the bundle. When the label field is wanted,
// but empty, it takes the <no label> value.
var listLineTemplateString string
if !withLabels {
listLineTemplateString = `{{.ID}} , {{.Timestamp}} , {{.Message}}`
} else {
// template with labels
listLineTemplateString = `{{.ID}} ,{{.Labels}}, {{.Timestamp}} , {{.Message}}`
}
return template.Must(template.New("list line").Parse(listLineTemplateString))
}

func setLatestOrLabelledBundle(ctx context.Context, remote context2.Stores) error {
Expand Down Expand Up @@ -69,3 +78,18 @@ func setLatestOrLabelledBundle(ctx context.Context, remote context2.Stores) erro
log.Printf("Using bundle: %s", datamonFlags.bundle.ID)
return nil
}

// displayBundleLabels constructs a string representation of a list of labels associated to a bundle
func displayBundleLabels(bundleID string, labels []model.LabelDescriptor) string {
bundleLabels := make([]string, 0, len(labels))
for _, label := range labels {
if label.BundleID == bundleID {
bundleLabels = append(bundleLabels, label.Name)
}
}
if len(bundleLabels) > 0 {
// using ";" to keep simple split rule on main fields
return "(" + strings.Join(bundleLabels, ";") + ")"
}
return "<no label>"
}
21 changes: 20 additions & 1 deletion cmd/datamon/cmd/bundle_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/oneconcern/datamon/pkg/core"
status "github.com/oneconcern/datamon/pkg/core/status"
"github.com/oneconcern/datamon/pkg/errors"
"github.com/oneconcern/datamon/pkg/model"

"github.com/spf13/cobra"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -57,8 +58,25 @@ exits with ENOENT status otherwise.`,
return
}

var labels []model.LabelDescriptor
if datamonFlags.bundle.WithLabels {
// optionally starts by retrieving labels on this repo
labels = getLabels(remoteStores)
}

var buf bytes.Buffer
err = bundleDescriptorTemplate.Execute(&buf, bundle.BundleDescriptor)
if labels != nil {
data := struct {
model.BundleDescriptor
Labels string
}{
BundleDescriptor: bundle.BundleDescriptor,
}
data.Labels = displayBundleLabels(bundle.BundleDescriptor.ID, labels)
err = bundleDescriptorTemplate(true).Execute(&buf, data)
} else {
err = bundleDescriptorTemplate(false).Execute(&buf, bundle.BundleDescriptor)
}
if err != nil {
log.Println("executing template:", err)
}
Expand All @@ -76,6 +94,7 @@ func init() {

addBundleFlag(GetBundleCommand)
addLabelNameFlag(GetBundleCommand)
addWithLabelFlag(GetBundleCommand)

bundleCmd.AddCommand(GetBundleCommand)
}
43 changes: 34 additions & 9 deletions cmd/datamon/cmd/bundle_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,31 @@ import (
"github.com/spf13/cobra"
)

func applyBundleTemplate(bundle model.BundleDescriptor) error {
var buf bytes.Buffer
if err := bundleDescriptorTemplate.Execute(&buf, bundle); err != nil {
// NOTE(frederic): to be discussed - PR#267 introduced a change here
// by stopping upon errors while it was previously non-blocking
return fmt.Errorf("executing template: %w", err)
func applyBundleTemplate(labels []model.LabelDescriptor) func(model.BundleDescriptor) error {
return func(bundle model.BundleDescriptor) error {
var (
buf bytes.Buffer
err error
)

if labels != nil {
data := struct {
model.BundleDescriptor
Labels string
}{
BundleDescriptor: bundle,
}
data.Labels = displayBundleLabels(bundle.ID, labels)
err = bundleDescriptorTemplate(true).Execute(&buf, data)
} else {
err = bundleDescriptorTemplate(false).Execute(&buf, bundle)
}
if err != nil {
return fmt.Errorf("executing template: %w", err)
}
log.Println(buf.String())
return nil
}
log.Println(buf.String())
return nil
}

// BundleListCommand describes the CLI command for listing bundles
Expand All @@ -39,7 +55,15 @@ This is analogous to the "git log" command. The bundle ID works like a git commi
wrapFatalln("create remote stores", err)
return
}
err = core.ListBundlesApply(datamonFlags.repo.RepoName, remoteStores, applyBundleTemplate,

var labels []model.LabelDescriptor
if datamonFlags.bundle.WithLabels {
// optionally starts by retrieving labels on this repo
labels = getLabels(remoteStores)
}

err = core.ListBundlesApply(datamonFlags.repo.RepoName, remoteStores,
applyBundleTemplate(labels),
core.ConcurrentList(datamonFlags.core.ConcurrencyFactor),
core.BatchSize(datamonFlags.core.BatchSize))
if err != nil {
Expand All @@ -59,6 +83,7 @@ func init() {

addCoreConcurrencyFactorFlag(BundleListCommand, 500)
addBatchSizeFlag(BundleListCommand)
addWithLabelFlag(BundleListCommand)

bundleCmd.AddCommand(BundleListCommand)
}
52 changes: 46 additions & 6 deletions cmd/datamon/cmd/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ type bundleListEntry struct {
hash string
message string
time time.Time
labels string
}

type bundleListEntries []bundleListEntry
Expand All @@ -369,16 +370,17 @@ func (b bundleListEntries) Last() bundleListEntry {
return b[len(b)-1]
}

func listBundles(t *testing.T, repoName string) (bundleListEntries, error) {
func listBundles(t *testing.T, repoName string, flags ...string) (bundleListEntries, error) {
r, w, err := os.Pipe()
if err != nil {
panic(err)
}
log.SetOutput(w)
runCmd(t, []string{"bundle",
runCmd(t, append([]string{"bundle",
"list",
"--repo", repoName,
}, "list bundles", false)
}, flags...),
"list bundles", false)
log.SetOutput(os.Stdout)
w.Close()
//
Expand All @@ -387,16 +389,31 @@ func listBundles(t *testing.T, repoName string) (bundleListEntries, error) {
bles := make(bundleListEntries, 0)
for _, line := range getDataLogLines(t, string(lb), []string{}) {
sl := strings.Split(line, ",")
t, err := time.Parse(timeForm, strings.TrimSpace(sl[1]))

// updates expectations about format, depending on flags
expectLabels := false
offset := 0
for _, flag := range flags {
if flag == "--with-labels" {
expectLabels = true
offset = 1
break
}
}

t, err := time.Parse(timeForm, strings.TrimSpace(sl[1+offset]))
if err != nil {
return nil, err
}
rle := bundleListEntry{
rawLine: line,
hash: strings.TrimSpace(sl[0]), // bundle ID
message: strings.TrimSpace(sl[2]),
message: strings.TrimSpace(sl[2+offset]),
time: t,
}
if expectLabels {
rle.labels = strings.TrimSpace(sl[1])
}
bles = append(bles, rle)
}
// bundles are ordered by lexicographic order of bundle IDs
Expand Down Expand Up @@ -504,6 +521,7 @@ func TestGetLabel(t *testing.T) {
"--description", "testing",
"--repo", repo1,
}, "create first test repo", false)

runCmd(t, []string{"label",
"get",
"--repo", repo1,
Expand All @@ -513,6 +531,7 @@ func TestGetLabel(t *testing.T) {
"ENOENT on nonexistant label")
files := testUploadTrees[0]
file := files[0]

runCmd(t, []string{"bundle",
"upload",
"--path", dirPathStr(t, file),
Expand All @@ -521,11 +540,26 @@ func TestGetLabel(t *testing.T) {
"--label", label,
"--concurrency-factor", concurrencyFactor,
}, "upload bundle at "+dirPathStr(t, file), false)

runCmd(t, []string{"label",
"get",
"--repo", repo1,
"--label", label,
}, "get label", false)

// query bundles with labels
bundles, err := listBundles(t, repo1, "--with-labels")
require.NoError(t, err)
bundle := bundles.Last()
require.Equal(t, "("+label+")", bundle.labels)

runCmd(t, []string{"bundle",
"get",
"--repo", repo1,
"--bundle", bundle.hash,
"--with-labels",
}, "("+label+")", false)
require.NoError(t, err)
}

type labelListEntry struct {
Expand Down Expand Up @@ -643,7 +677,13 @@ func TestSetLabel(t *testing.T) {
ll = listLabels(t, repo1, "")
require.Equal(t, len(ll), 0, "no labels created yet")

bundles, err := listBundles(t, repo1)
// query bundles with labels when no label set
bundles, err := listBundles(t, repo1, "--with-labels")
require.NoError(t, err)
bundle := bundles.Last()
require.Equal(t, "<no label>", bundle.labels)

bundles, err = listBundles(t, repo1)
require.NoError(t, err, "error out of listBundles() test helper")
require.Equal(t, 1, bundles.Len(), "bundle count in test repo")

Expand Down
7 changes: 7 additions & 0 deletions cmd/datamon/cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type flagsT struct {
SkipOnError bool
ConcurrencyFactor int
NameFilter string
WithLabels bool
}
web struct {
port int
Expand Down Expand Up @@ -205,6 +206,12 @@ func addLabelPrefixFlag(cmd *cobra.Command) string {
return prefixString
}

func addWithLabelFlag(cmd *cobra.Command) string {
withLabels := "with-labels"
cmd.Flags().BoolVar(&datamonFlags.bundle.WithLabels, withLabels, false, "Include labels in the returned bundle metadata")
return withLabels
}

func addRepoNameOptionFlag(cmd *cobra.Command) string {
repo := "repo"
cmd.Flags().StringVar(&datamonFlags.repo.RepoName, repo, "", "The name of this repository")
Expand Down
14 changes: 14 additions & 0 deletions cmd/datamon/cmd/label.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package cmd
import (
"text/template"

context2 "github.com/oneconcern/datamon/pkg/context"
"github.com/oneconcern/datamon/pkg/core"
"github.com/oneconcern/datamon/pkg/model"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -36,3 +39,14 @@ func init() {
return template.Must(template.New("list line").Parse(listLineTemplateString))
}()
}

// getLabels is a helper to synchronously retrieve labels and enrich the result from other commands
func getLabels(remoteStores context2.Stores) []model.LabelDescriptor {
labels, err := core.ListLabels(datamonFlags.repo.RepoName, remoteStores, "",
core.ConcurrentList(datamonFlags.core.ConcurrencyFactor),
core.BatchSize(datamonFlags.core.BatchSize))
if err != nil {
wrapFatalln("could not download label list: %w", err)
}
return labels
}
1 change: 1 addition & 0 deletions docs/usage/datamon_bundle_get.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ datamon bundle get [flags]
-h, --help help for get
--label string The human-readable name of a label
--repo string The name of this repository
--with-labels Include labels in the returned bundle metadata
```

### Options inherited from parent commands
Expand Down
1 change: 1 addition & 0 deletions docs/usage/datamon_bundle_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ datamon bundle list [flags]
--concurrency-factor int Heuristic on the amount of concurrency used by core operations. Concurrent retrieval of metadata is capped by the 'batch-size' parameter. Turn this value down to use less memory, increase for faster operations. (default 500)
-h, --help help for list
--repo string The name of this repository
--with-labels Include labels in the returned bundle metadata
```

### Options inherited from parent commands
Expand Down
5 changes: 1 addition & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fredbi/go-github-selfupdate v1.2.0 h1:4wmtpH3u0JGRs7xWf29kdbb6wd2Om6t0DdyD+OWDFek=
github.com/fredbi/go-github-selfupdate v1.2.0/go.mod h1:BZHw2H9JIH7wdyda/btlRRD+sgudn1RO1NbnvY27/Ng=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
Expand Down Expand Up @@ -213,6 +211,7 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
Expand All @@ -221,8 +220,6 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rhysd/go-github-selfupdate v1.1.0 h1:+aMomy69YCYxJ6kr13nYIgAJWSB1kHK5M5YpbmjQkWo=
github.com/rhysd/go-github-selfupdate v1.1.0/go.mod h1:jbfShZ+Nl3IHUgr77kwQjObcWf1z961UAoD6p5LrPBU=
github.com/rhysd/go-github-selfupdate v1.2.1 h1:CpU7O4BcHhvcXEBsQSKD50uQ1o4/qv/15Ev6TPAtU3s=
github.com/rhysd/go-github-selfupdate v1.2.1/go.mod h1:BZHw2H9JIH7wdyda/btlRRD+sgudn1RO1NbnvY27/Ng=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
Expand Down

0 comments on commit 16ae3c3

Please sign in to comment.