Add -filter to CLI listing and website docs (#967)

* Add -filter to CLI listing.

This doesn't include docs/Changelog yet, that will come shortly.

* Add changelog

* Add website docs and fix a curl output bug
This commit is contained in:
Jeff Mitchell authored and GitHub committed 2021-03-01 09:23:17 -08:00
1 parent a4005b04f3
commit 519efa6dd1
26 files changed
+199 -20

No files matched your search

+7
View File
@@ -24,6 +24,13 @@ All of these changes are from [PR
### New and Improved
* list filtering: Listing now supports filtering results before being returned
to the user. The filtering takes place server side and uses boolean
expressions against the JSON representation of returned items. See [the
documentation](https://www.boundaryproject.io/docs/concepts/filtering/resource-listing)
for more details. ([PR](https://github.com/hashicorp/boundary/pull/952))
([PR](https://github.com/hashicorp/boundary/pull/957))
([PR](https://github.com/hashicorp/boundary/pull/967))
* server: Officially support reloading TLS parameters on `SIGHUP`. (This likely
worked before but wasn't fully tested.)
([PR](https://github.com/hashicorp/boundary/pull/959))
+4 -3
View File
@@ -57,7 +57,7 @@ func (d *OutputStringError) parseRequest() {
h = fmt.Sprintf("Bearer $(boundary config get-token -keyring-type %s -token-name %s)", keyringType, tokenName)
}
}
d.parsedCurlString = fmt.Sprintf("%s-H \"%s: %s\" ", d.parsedCurlString, k, h)
d.parsedCurlString = fmt.Sprintf("%s-H \"%s: %s\"", d.parsedCurlString, k, h)
}
}
@@ -65,10 +65,11 @@ func (d *OutputStringError) parseRequest() {
// We need to escape single quotes since that's what we're using to
// quote the body
escapedBody := strings.Replace(string(body), "'", "'\"'\"'", -1)
d.parsedCurlString = fmt.Sprintf("%s-d '%s' ", d.parsedCurlString, escapedBody)
d.parsedCurlString = fmt.Sprintf(" %s-d '%s'", d.parsedCurlString, escapedBody)
}
d.parsedCurlString = fmt.Sprintf("%s%s", d.parsedCurlString, d.Request.URL.String())
// Filters can have shell characters so we use single quotes to surround the URL
d.parsedCurlString = fmt.Sprintf("%s '%s'", d.parsedCurlString, d.Request.URL.String())
}
func (d *OutputStringError) CurlString() string {
+1
View File
@@ -77,6 +77,7 @@ type Command struct {
FlagHostCatalogId string
FlagVersion int
FlagRecursive bool
FlagFilter string
client *api.Client
}
@@ -94,7 +94,7 @@ var flagsMap = map[string][]string{
"delete": {"id"},
"list": {"auth-method-id"},
"list": {"auth-method-id", "filter"},
}
func (c *Command) Flags() *base.FlagSets {
@@ -176,6 +176,10 @@ func (c *Command) Run(args []string) int {
opts = append(opts, accounts.WithDescription(c.FlagDescription))
}
if c.FlagFilter != "" {
opts = append(opts, accounts.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
@@ -159,6 +159,10 @@ func (c *PasswordCommand) Run(args []string) int {
opts = append(opts, accounts.WithDescription(c.FlagDescription))
}
if c.FlagFilter != "" {
opts = append(opts, accounts.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
@@ -92,7 +92,7 @@ var flagsMap = map[string][]string{
"delete": {"id"},
"list": {"scope-id", "recursive"},
"list": {"scope-id", "filter", "recursive"},
}
func (c *Command) Flags() *base.FlagSets {
@@ -163,6 +163,10 @@ func (c *Command) Run(args []string) int {
opts = append(opts, authmethods.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, authmethods.WithFilter(c.FlagFilter))
}
var version uint32
if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 {
@@ -164,6 +164,10 @@ func (c *PasswordCommand) Run(args []string) int {
opts = append(opts, authmethods.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, authmethods.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
@@ -92,7 +92,7 @@ var flagsMap = map[string][]string{
"delete": {"id"},
"list": {"scope-id", "recursive"},
"list": {"scope-id", "filter", "recursive"},
}
func (c *Command) Flags() *base.FlagSets {
@@ -159,6 +159,10 @@ func (c *Command) Run(args []string) int {
opts = append(opts, authtokens.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, authtokens.WithFilter(c.FlagFilter))
}
var version uint32
if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 {
@@ -104,7 +104,7 @@ var flagsMap = map[string][]string{
"delete": {"id"},
"list": {"scope-id", "recursive"},
"list": {"scope-id", "filter", "recursive"},
}
func (c *Command) Flags() *base.FlagSets {
@@ -195,6 +195,10 @@ func (c *Command) Run(args []string) int {
opts = append(opts, groups.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, groups.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
@@ -92,7 +92,7 @@ var flagsMap = map[string][]string{
"delete": {"id"},
"list": {"scope-id", "recursive"},
"list": {"scope-id", "filter", "recursive"},
}
func (c *Command) Flags() *base.FlagSets {
@@ -163,6 +163,10 @@ func (c *Command) Run(args []string) int {
opts = append(opts, hostcatalogs.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, hostcatalogs.WithFilter(c.FlagFilter))
}
var version uint32
if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 {
@@ -162,6 +162,10 @@ func (c *StaticCommand) Run(args []string) int {
opts = append(opts, hostcatalogs.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, hostcatalogs.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
+5 -1
View File
@@ -92,7 +92,7 @@ var flagsMap = map[string][]string{
"delete": {"id"},
"list": {"host-catalog-id"},
"list": {"host-catalog-id", "filter"},
}
func (c *Command) Flags() *base.FlagSets {
@@ -174,6 +174,10 @@ func (c *Command) Run(args []string) int {
opts = append(opts, hosts.WithDescription(c.FlagDescription))
}
if c.FlagFilter != "" {
opts = append(opts, hosts.WithFilter(c.FlagFilter))
}
var version uint32
if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 {
@@ -159,6 +159,10 @@ func (c *StaticCommand) Run(args []string) int {
opts = append(opts, hosts.WithDescription(c.FlagDescription))
}
if c.FlagFilter != "" {
opts = append(opts, hosts.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
@@ -94,7 +94,7 @@ var flagsMap = map[string][]string{
"delete": {"id"},
"list": {"host-catalog-id"},
"list": {"host-catalog-id", "filter"},
}
func (c *Command) Flags() *base.FlagSets {
@@ -176,6 +176,10 @@ func (c *Command) Run(args []string) int {
opts = append(opts, hostsets.WithDescription(c.FlagDescription))
}
if c.FlagFilter != "" {
opts = append(opts, hostsets.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
@@ -157,6 +157,10 @@ func (c *StaticCommand) Run(args []string) int {
opts = append(opts, hostsets.WithDescription(c.FlagDescription))
}
if c.FlagFilter != "" {
opts = append(opts, hostsets.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
+5 -1
View File
@@ -104,7 +104,7 @@ var flagsMap = map[string][]string{
"delete": {"id"},
"list": {"scope-id", "recursive"},
"list": {"scope-id", "filter", "recursive"},
}
func (c *Command) Flags() *base.FlagSets {
@@ -195,6 +195,10 @@ func (c *Command) Run(args []string) int {
opts = append(opts, roles.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, roles.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
@@ -104,7 +104,7 @@ var flagsMap = map[string][]string{
"delete": {"id"},
"list": {"scope-id", "recursive"},
"list": {"scope-id", "filter", "recursive"},
}
func (c *Command) Flags() *base.FlagSets {
@@ -195,6 +195,10 @@ func (c *Command) Run(args []string) int {
opts = append(opts, scopes.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, scopes.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
@@ -104,10 +104,10 @@ func TestServer_ReloadListener(t *testing.T) {
// Setup initial certs
inBytes, err := ioutil.ReadFile(wd + "bundle1.pem")
require.NoError(err)
require.NoError(ioutil.WriteFile(td+"/bundle.pem", inBytes, 0777))
require.NoError(ioutil.WriteFile(td+"/bundle.pem", inBytes, 0o777))
relHcl := fmt.Sprintf(reloadConfig, cmd.DatabaseUrl, controllerKey, workerAuthKey, recoveryKey, td, td)
require.NoError(ioutil.WriteFile(td+"/reload.hcl", []byte(relHcl), 0777))
require.NoError(ioutil.WriteFile(td+"/reload.hcl", []byte(relHcl), 0o777))
// Populate CA pool
inBytes, _ = ioutil.ReadFile(td + "/bundle.pem")
@@ -146,7 +146,7 @@ func TestServer_ReloadListener(t *testing.T) {
inBytes, err = ioutil.ReadFile(wd + "bundle2.pem")
require.NoError(err)
require.NoError(ioutil.WriteFile(td+"/bundle.pem", inBytes, 0777))
require.NoError(ioutil.WriteFile(td+"/bundle.pem", inBytes, 0o777))
cmd.SighupCh <- struct{}{}
select {
@@ -86,7 +86,7 @@ var flagsMap = map[string][]string{
"read": {"id"},
"list": {"scope-id", "recursive"},
"list": {"scope-id", "filter", "recursive"},
}
func (c *Command) Flags() *base.FlagSets {
@@ -153,6 +153,10 @@ func (c *Command) Run(args []string) int {
opts = append(opts, sessions.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, sessions.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
@@ -95,7 +95,7 @@ var flagsMap = map[string][]string{
"delete": {"id"},
"list": {"scope-id", "recursive"},
"list": {"scope-id", "filter", "recursive"},
}
func (c *Command) Flags() *base.FlagSets {
@@ -182,6 +182,10 @@ func (c *Command) Run(args []string) int {
opts = append(opts, targets.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, targets.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
@@ -164,6 +164,10 @@ func (c *TcpCommand) Run(args []string) int {
opts = append(opts, targets.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, targets.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
+5 -1
View File
@@ -104,7 +104,7 @@ var flagsMap = map[string][]string{
"delete": {"id"},
"list": {"scope-id", "recursive"},
"list": {"scope-id", "filter", "recursive"},
}
func (c *Command) Flags() *base.FlagSets {
@@ -195,6 +195,10 @@ func (c *Command) Run(args []string) int {
opts = append(opts, users.WithRecursive(true))
}
if c.FlagFilter != "" {
opts = append(opts, users.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
+6
View File
@@ -71,6 +71,12 @@ func PopulateCommonFlags(c *base.Command, f *base.FlagSet, resourceType string,
Target: &c.FlagRecursive,
Usage: "If set, the list operation will be applied recursively into child scopes, if supported by the type.",
})
case "filter":
f.StringVar(&base.StringVar{
Name: "filter",
Target: &c.FlagFilter,
Usage: "If set, the list operation will be filtered before being returned. The filter operates against each item in the list. Using single quotes is recommended as filters contain double quotes. See https://www.boundaryproject.io/docs/concepts/filtering/resource-listing for details.",
})
}
}
}
+5 -1
View File
@@ -183,7 +183,7 @@ var flags{{ camelCase .SubActionPrefix }}Map = map[string][]string{
"delete": {"id"},
{{ end }}
{{ if eq $action "list" }}
"list": { "{{ kebabCase $input.Container }}-id" {{ if (eq $input.Container "Scope") }}, "recursive"{{ end }} },
"list": { "{{ kebabCase $input.Container }}-id", "filter" {{ if (eq $input.Container "Scope") }}, "recursive"{{ end }} },
{{ end }}
{{ end }}
}
@@ -296,6 +296,10 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
}
{{ end }}
if c.FlagFilter != "" {
opts = append(opts, {{ .Pkg }}.WithFilter(c.FlagFilter))
}
{{ if .HasScopeName }}
switch c.FlagScopeName {
case "":
@@ -0,0 +1,88 @@
---
layout: docs
page_title: Filtering - Resource Listing
sidebar_title: Resource Listing
description: |-
How to use filter list responses coming back from Boundary.
---
This page describes how to use filters when listing resources. This can be used
to reduce the returned set of resources when performing a list operation.
~> This feature is intended to provide a userful service to clients; it does not
affect the database queries generated for the operation and as such is not
designed to provide greater efficiency.
# List Filtering
Starting in Boundary 0.1.8, when running a list action, a filter can be
specified. It uses the standard [filter syntax](/docs/concepts/filtering) used
elsewhere in Boundary. Unless otherwise specified for a given list endpoint, the
list of items being returned is looped through and the filter is run on the JSON
representation of that item. A good way to see what that data looks like is by
looking at representative JSON output on the command line; for example, the
following is the output of `boundary targets list -scope-id p_1234567890 -format json` on a dev instance (piped through `jq` for readability):
```json
[
{
"id": "ttcp_1234567890",
"scope_id": "p_1234567890",
"scope": {
"id": "p_1234567890",
"type": "project",
"name": "Generated project scope",
"description": "Provides an initial project scope in Boundary",
"parent_scope_id": "o_1234567890"
},
"name": "Generated target",
"description": "Provides an initial target in Boundary",
"created_time": "2021-02-24T22:19:50.640476Z",
"updated_time": "2021-02-24T22:19:50.640476Z",
"version": 1,
"type": "tcp",
"session_max_seconds": 28800,
"session_connection_limit": -1,
"attributes": {
"default_port": 22
},
"authorized_actions": [
"read",
"update",
"delete",
"add-host-sets",
"set-host-sets",
"remove-host-sets",
"authorize-session"
]
}
]
```
As the filter tests each entry being returned, it places the data under test
within the filter at `/item`.
On the CLI a filter can be given via `-filter`.
~> Double quotes are part of the filter syntax; when using the CLI, it is likely
easier to surround the filter with single quotes than to deal with escaping
double quotes.
When using the HTTP API, it is a `filter` query parameter.
~> Ensure that the query parameter is properly escaped! Most HTTP libraries will
do this for you. If you're having trouble, try using the `-output-curl-string`
flag with the Boundary CLI:
```
$ boundary targets list -scope-id p_1234567890 -format json -filter '"authorize-session" in "/item/authorized_actions"' -output-curl-string
curl -H "Authorization: Bearer $(boundary config get-token -keyring-type pass -token-name default)"-H "Content-Type: application/json" 'http://127.0.0.1:9200/v1/targets?filter=%22authorize-session%22+in+%22%2Fitem%2Fauthorized_actions%22&scope_id=p_1234567890'
```
Following are some examples.
- Resources in which the user is allowed to run an "update" action:
`"update" in "/item/authorized_actions"`
- Resources matching a name pattern, but only those within an organization
scope: `"/item/name" matches "groupa-*" and "/item/scope/type" == "org"`
+1 -1
View File
@@ -44,7 +44,7 @@ export default [
},
{
category: 'filtering',
content: ['worker-tags'],
content: ['resource-listing', 'worker-tags'],
},
],
},