mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	Add "policy" subcommand
This commit is contained in:
		
							
								
								
									
										52
									
								
								command/policies_deprecated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								command/policies_deprecated.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/mitchellh/cli" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Deprecation | ||||||
|  | // TODO: remove in 0.9.0 | ||||||
|  |  | ||||||
|  | var _ cli.Command = (*PoliciesDeprecatedCommand)(nil) | ||||||
|  |  | ||||||
|  | type PoliciesDeprecatedCommand struct { | ||||||
|  | 	*BaseCommand | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PoliciesDeprecatedCommand) Synopsis() string { return "" } | ||||||
|  |  | ||||||
|  | func (c *PoliciesDeprecatedCommand) Help() string { | ||||||
|  | 	return (&PolicyListCommand{ | ||||||
|  | 		BaseCommand: c.BaseCommand, | ||||||
|  | 	}).Help() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PoliciesDeprecatedCommand) Run(args []string) int { | ||||||
|  | 	oargs := args | ||||||
|  |  | ||||||
|  | 	f := c.flagSet(FlagSetHTTP) | ||||||
|  | 	if err := f.Parse(args); err != nil { | ||||||
|  | 		c.UI.Error(err.Error()) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  | 	args = f.Args() | ||||||
|  |  | ||||||
|  | 	// Got an arg, this is trying to read a policy | ||||||
|  | 	if len(args) > 0 { | ||||||
|  | 		return (&PolicyReadCommand{ | ||||||
|  | 			BaseCommand: &BaseCommand{ | ||||||
|  | 				UI:     c.UI, | ||||||
|  | 				client: c.client, | ||||||
|  | 			}, | ||||||
|  | 		}).Run(oargs) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// No args, probably ran "vault policies" and we want to translate that to | ||||||
|  | 	// "vault policy list" | ||||||
|  | 	return (&PolicyListCommand{ | ||||||
|  | 		BaseCommand: &BaseCommand{ | ||||||
|  | 			UI:     c.UI, | ||||||
|  | 			client: c.client, | ||||||
|  | 		}, | ||||||
|  | 	}).Run(oargs) | ||||||
|  | } | ||||||
							
								
								
									
										96
									
								
								command/policies_deprecated_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								command/policies_deprecated_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/mitchellh/cli" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func testPoliciesDeprecatedCommand(tb testing.TB) (*cli.MockUi, *PoliciesDeprecatedCommand) { | ||||||
|  | 	tb.Helper() | ||||||
|  |  | ||||||
|  | 	ui := cli.NewMockUi() | ||||||
|  | 	return ui, &PoliciesDeprecatedCommand{ | ||||||
|  | 		BaseCommand: &BaseCommand{ | ||||||
|  | 			UI: ui, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPoliciesDeprecatedCommand_Run(t *testing.T) { | ||||||
|  | 	t.Parallel() | ||||||
|  |  | ||||||
|  | 	// TODO: remove in 0.9.0 | ||||||
|  | 	t.Run("deprecated_arg", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		client, closer := testVaultServer(t) | ||||||
|  | 		defer closer() | ||||||
|  |  | ||||||
|  | 		ui, cmd := testPoliciesDeprecatedCommand(t) | ||||||
|  | 		cmd.client = client | ||||||
|  |  | ||||||
|  | 		// vault policies ARG -> vault policy read ARG | ||||||
|  | 		code := cmd.Run([]string{"default"}) | ||||||
|  | 		if exp := 0; code != exp { | ||||||
|  | 			t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String()) | ||||||
|  | 		} | ||||||
|  | 		stdout := ui.OutputWriter.String() | ||||||
|  |  | ||||||
|  | 		if expected := "token/"; !strings.Contains(stdout, expected) { | ||||||
|  | 			t.Errorf("expected %q to contain %q", stdout, expected) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("deprecated_no_args", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		client, closer := testVaultServer(t) | ||||||
|  | 		defer closer() | ||||||
|  |  | ||||||
|  | 		ui, cmd := testPoliciesDeprecatedCommand(t) | ||||||
|  | 		cmd.client = client | ||||||
|  |  | ||||||
|  | 		// vault policies -> vault policy list | ||||||
|  | 		code := cmd.Run([]string{}) | ||||||
|  | 		if exp := 0; code != exp { | ||||||
|  | 			t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String()) | ||||||
|  | 		} | ||||||
|  | 		stdout := ui.OutputWriter.String() | ||||||
|  |  | ||||||
|  | 		if expected := "root"; !strings.Contains(stdout, expected) { | ||||||
|  | 			t.Errorf("expected %q to contain %q", stdout, expected) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("deprecated_with_flags", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		client, closer := testVaultServer(t) | ||||||
|  | 		defer closer() | ||||||
|  |  | ||||||
|  | 		ui, cmd := testPoliciesDeprecatedCommand(t) | ||||||
|  | 		cmd.client = client | ||||||
|  |  | ||||||
|  | 		// vault policies -flag -> vault policy list | ||||||
|  | 		code := cmd.Run([]string{ | ||||||
|  | 			"-address", client.Address(), | ||||||
|  | 		}) | ||||||
|  | 		if exp := 0; code != exp { | ||||||
|  | 			t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String()) | ||||||
|  | 		} | ||||||
|  | 		stdout := ui.OutputWriter.String() | ||||||
|  |  | ||||||
|  | 		if expected := "root"; !strings.Contains(stdout, expected) { | ||||||
|  | 			t.Errorf("expected %q to contain %q", stdout, expected) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("no_tabs", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		_, cmd := testPoliciesDeprecatedCommand(t) | ||||||
|  | 		assertNoTabs(t, cmd) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								command/policy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								command/policy.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/mitchellh/cli" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var _ cli.Command = (*PolicyCommand)(nil) | ||||||
|  |  | ||||||
|  | // PolicyCommand is a Command that holds the audit commands | ||||||
|  | type PolicyCommand struct { | ||||||
|  | 	*BaseCommand | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyCommand) Synopsis() string { | ||||||
|  | 	return "Interact with policies" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyCommand) Help() string { | ||||||
|  | 	helpText := ` | ||||||
|  | Usage: vault policy <subcommand> [options] [args] | ||||||
|  |  | ||||||
|  |   This command groups subcommands for interacting with policies. Users can | ||||||
|  |   Users can write, read, and list policies in Vault. | ||||||
|  |  | ||||||
|  |   List all enabled policies: | ||||||
|  |  | ||||||
|  |       $ vault policy list | ||||||
|  |  | ||||||
|  |   Create a policy named "my-policy" from contents on local disk: | ||||||
|  |  | ||||||
|  |       $ vault policy write my-policy ./my-policy.hcl | ||||||
|  |  | ||||||
|  |   Delete the policy named my-policy: | ||||||
|  |  | ||||||
|  |       $ vault policy delete my-policy | ||||||
|  |  | ||||||
|  |   Please see the individual subcommand help for detailed usage information. | ||||||
|  | ` | ||||||
|  |  | ||||||
|  | 	return strings.TrimSpace(helpText) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyCommand) Run(args []string) int { | ||||||
|  | 	return cli.RunResultHelp | ||||||
|  | } | ||||||
| @@ -8,11 +8,9 @@ import ( | |||||||
| 	"github.com/posener/complete" | 	"github.com/posener/complete" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Ensure we are implementing the right interfaces. |  | ||||||
| var _ cli.Command = (*PolicyDeleteCommand)(nil) | var _ cli.Command = (*PolicyDeleteCommand)(nil) | ||||||
| var _ cli.CommandAutocomplete = (*PolicyDeleteCommand)(nil) | var _ cli.CommandAutocomplete = (*PolicyDeleteCommand)(nil) | ||||||
|  |  | ||||||
| // PolicyDeleteCommand is a Command that enables a new endpoint. |  | ||||||
| type PolicyDeleteCommand struct { | type PolicyDeleteCommand struct { | ||||||
| 	*BaseCommand | 	*BaseCommand | ||||||
| } | } | ||||||
| @@ -23,17 +21,17 @@ func (c *PolicyDeleteCommand) Synopsis() string { | |||||||
|  |  | ||||||
| func (c *PolicyDeleteCommand) Help() string { | func (c *PolicyDeleteCommand) Help() string { | ||||||
| 	helpText := ` | 	helpText := ` | ||||||
| Usage: vault policy-delete [options] NAME | Usage: vault policy delete [options] NAME | ||||||
|  |  | ||||||
|   Deletes a policy in the Vault server with the given name. Once the policy |   Deletes the policy named NAME in the Vault server. Once the policy is deleted, | ||||||
|   is deleted, all tokens associated with the policy will be affected |   all tokens associated with the policy are affected immediately. | ||||||
|   immediately. |  | ||||||
|  |  | ||||||
|   Delete the policy named "my-policy": |   Delete the policy named "my-policy": | ||||||
|  |  | ||||||
|       $ vault policy-delete my-policy |       $ vault policy delete my-policy | ||||||
|  |  | ||||||
|   For a full list of examples, please see the documentation. |   Note that it is not possible to delete the "default" or "root" policies. | ||||||
|  |   These are built-in policies. | ||||||
|  |  | ||||||
| ` + c.Flags().Help() | ` + c.Flags().Help() | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										109
									
								
								command/policy_fmt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								command/policy_fmt.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | |||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/hcl/hcl/printer" | ||||||
|  | 	"github.com/hashicorp/vault/vault" | ||||||
|  | 	"github.com/mitchellh/cli" | ||||||
|  | 	homedir "github.com/mitchellh/go-homedir" | ||||||
|  | 	"github.com/posener/complete" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var _ cli.Command = (*PolicyFmtCommand)(nil) | ||||||
|  | var _ cli.CommandAutocomplete = (*PolicyFmtCommand)(nil) | ||||||
|  |  | ||||||
|  | type PolicyFmtCommand struct { | ||||||
|  | 	*BaseCommand | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyFmtCommand) Synopsis() string { | ||||||
|  | 	return "Formats a policy on disk" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyFmtCommand) Help() string { | ||||||
|  | 	helpText := ` | ||||||
|  | Usage: vault policy fmt [options] PATH | ||||||
|  |  | ||||||
|  |   Formats a local policy file to the policy specification. This command will | ||||||
|  |   overwrite the file at the given PATH with the properly-formatted policy | ||||||
|  |   file contents. | ||||||
|  |  | ||||||
|  |   Format the local file "my-policy.hcl" as a policy file: | ||||||
|  |  | ||||||
|  |       $ vault policy fmt my-policy.hcl | ||||||
|  |  | ||||||
|  | ` + c.Flags().Help() | ||||||
|  |  | ||||||
|  | 	return strings.TrimSpace(helpText) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyFmtCommand) Flags() *FlagSets { | ||||||
|  | 	return c.flagSet(FlagSetNone) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyFmtCommand) AutocompleteArgs() complete.Predictor { | ||||||
|  | 	return complete.PredictFiles("*.hcl") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyFmtCommand) AutocompleteFlags() complete.Flags { | ||||||
|  | 	return c.Flags().Completions() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyFmtCommand) Run(args []string) int { | ||||||
|  | 	f := c.Flags() | ||||||
|  |  | ||||||
|  | 	if err := f.Parse(args); err != nil { | ||||||
|  | 		c.UI.Error(err.Error()) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	args = f.Args() | ||||||
|  | 	switch { | ||||||
|  | 	case len(args) < 1: | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args))) | ||||||
|  | 		return 1 | ||||||
|  | 	case len(args) > 1: | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args))) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Get the filepath, accounting for ~ and stuff | ||||||
|  | 	path, err := homedir.Expand(strings.TrimSpace(args[0])) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Failed to expand path: %s", err)) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Read the entire contents into memory - it would be nice if we could use | ||||||
|  | 	// a buffer, but hcl wants the full contents. | ||||||
|  | 	b, err := ioutil.ReadFile(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Error reading source file: %s", err)) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Actually parse the policy | ||||||
|  | 	if _, err := vault.Parse(string(b)); err != nil { | ||||||
|  | 		c.UI.Error(err.Error()) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Generate final contents | ||||||
|  | 	result, err := printer.Format(b) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Error printing result: %s", err)) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Write them back out | ||||||
|  | 	if err := ioutil.WriteFile(path, result, 0644); err != nil { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Error writing result: %s", err)) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.UI.Output(fmt.Sprintf("Success! Formatted policy: %s", path)) | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
							
								
								
									
										213
									
								
								command/policy_fmt_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								command/policy_fmt_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | |||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/mitchellh/cli" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func testPolicyFmtCommand(tb testing.TB) (*cli.MockUi, *PolicyFmtCommand) { | ||||||
|  | 	tb.Helper() | ||||||
|  |  | ||||||
|  | 	ui := cli.NewMockUi() | ||||||
|  | 	return ui, &PolicyFmtCommand{ | ||||||
|  | 		BaseCommand: &BaseCommand{ | ||||||
|  | 			UI: ui, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPolicyFmtCommand_Run(t *testing.T) { | ||||||
|  | 	t.Parallel() | ||||||
|  |  | ||||||
|  | 	cases := []struct { | ||||||
|  | 		name string | ||||||
|  | 		args []string | ||||||
|  | 		out  string | ||||||
|  | 		code int | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			"not_enough_args", | ||||||
|  | 			[]string{}, | ||||||
|  | 			"Not enough arguments", | ||||||
|  | 			1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"too_many_args", | ||||||
|  | 			[]string{"foo", "bar"}, | ||||||
|  | 			"Too many arguments", | ||||||
|  | 			1, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	t.Run("validations", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		for _, tc := range cases { | ||||||
|  | 			tc := tc | ||||||
|  |  | ||||||
|  | 			t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 				t.Parallel() | ||||||
|  |  | ||||||
|  | 				ui, cmd := testPolicyFmtCommand(t) | ||||||
|  |  | ||||||
|  | 				code := cmd.Run(tc.args) | ||||||
|  | 				if code != tc.code { | ||||||
|  | 					t.Errorf("expected %d to be %d", code, tc.code) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				combined := ui.OutputWriter.String() + ui.ErrorWriter.String() | ||||||
|  | 				if !strings.Contains(combined, tc.out) { | ||||||
|  | 					t.Errorf("expected %q to contain %q", combined, tc.out) | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("default", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		policy := strings.TrimSpace(` | ||||||
|  | path "secret" { | ||||||
|  |   capabilities  =           ["create",    "update","delete"] | ||||||
|  |  | ||||||
|  | } | ||||||
|  | `) | ||||||
|  |  | ||||||
|  | 		f, err := ioutil.TempFile("", "") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		defer os.Remove(f.Name()) | ||||||
|  | 		if _, err := f.Write([]byte(policy)); err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		f.Close() | ||||||
|  |  | ||||||
|  | 		_, cmd := testPolicyFmtCommand(t) | ||||||
|  |  | ||||||
|  | 		code := cmd.Run([]string{ | ||||||
|  | 			f.Name(), | ||||||
|  | 		}) | ||||||
|  | 		if exp := 0; code != exp { | ||||||
|  | 			t.Errorf("expected %d to be %d", code, exp) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		expected := strings.TrimSpace(` | ||||||
|  | path "secret" { | ||||||
|  |   capabilities = ["create", "update", "delete"] | ||||||
|  | } | ||||||
|  | `) + "\n" | ||||||
|  |  | ||||||
|  | 		contents, err := ioutil.ReadFile(f.Name()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		if string(contents) != expected { | ||||||
|  | 			t.Errorf("expected %q to be %q", string(contents), expected) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("bad_hcl", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		policy := `dafdaf` | ||||||
|  |  | ||||||
|  | 		f, err := ioutil.TempFile("", "") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		defer os.Remove(f.Name()) | ||||||
|  | 		if _, err := f.Write([]byte(policy)); err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		f.Close() | ||||||
|  |  | ||||||
|  | 		ui, cmd := testPolicyFmtCommand(t) | ||||||
|  |  | ||||||
|  | 		code := cmd.Run([]string{ | ||||||
|  | 			f.Name(), | ||||||
|  | 		}) | ||||||
|  | 		if exp := 1; code != exp { | ||||||
|  | 			t.Errorf("expected %d to be %d", code, exp) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		stderr := ui.ErrorWriter.String() | ||||||
|  | 		expected := "Failed to parse policy" | ||||||
|  | 		if !strings.Contains(stderr, expected) { | ||||||
|  | 			t.Errorf("expected %q to include %q", stderr, expected) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("bad_policy", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		policy := `banana "foo" {}` | ||||||
|  |  | ||||||
|  | 		f, err := ioutil.TempFile("", "") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		defer os.Remove(f.Name()) | ||||||
|  | 		if _, err := f.Write([]byte(policy)); err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		f.Close() | ||||||
|  |  | ||||||
|  | 		ui, cmd := testPolicyFmtCommand(t) | ||||||
|  |  | ||||||
|  | 		code := cmd.Run([]string{ | ||||||
|  | 			f.Name(), | ||||||
|  | 		}) | ||||||
|  | 		if exp := 1; code != exp { | ||||||
|  | 			t.Errorf("expected %d to be %d", code, exp) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		stderr := ui.ErrorWriter.String() | ||||||
|  | 		expected := "Failed to parse policy" | ||||||
|  | 		if !strings.Contains(stderr, expected) { | ||||||
|  | 			t.Errorf("expected %q to include %q", stderr, expected) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("bad_policy", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		policy := `path "secret/" { capabilities = ["bogus"] }` | ||||||
|  |  | ||||||
|  | 		f, err := ioutil.TempFile("", "") | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		defer os.Remove(f.Name()) | ||||||
|  | 		if _, err := f.Write([]byte(policy)); err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		f.Close() | ||||||
|  |  | ||||||
|  | 		ui, cmd := testPolicyFmtCommand(t) | ||||||
|  |  | ||||||
|  | 		code := cmd.Run([]string{ | ||||||
|  | 			f.Name(), | ||||||
|  | 		}) | ||||||
|  | 		if exp := 1; code != exp { | ||||||
|  | 			t.Errorf("expected %d to be %d", code, exp) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		stderr := ui.ErrorWriter.String() | ||||||
|  | 		expected := "Failed to parse policy" | ||||||
|  | 		if !strings.Contains(stderr, expected) { | ||||||
|  | 			t.Errorf("expected %q to include %q", stderr, expected) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("no_tabs", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		_, cmd := testPolicyFmtCommand(t) | ||||||
|  | 		assertNoTabs(t, cmd) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @@ -8,11 +8,9 @@ import ( | |||||||
| 	"github.com/posener/complete" | 	"github.com/posener/complete" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Ensure we are implementing the right interfaces. |  | ||||||
| var _ cli.Command = (*PolicyListCommand)(nil) | var _ cli.Command = (*PolicyListCommand)(nil) | ||||||
| var _ cli.CommandAutocomplete = (*PolicyListCommand)(nil) | var _ cli.CommandAutocomplete = (*PolicyListCommand)(nil) | ||||||
|  |  | ||||||
| // PolicyListCommand is a Command that enables a new endpoint. |  | ||||||
| type PolicyListCommand struct { | type PolicyListCommand struct { | ||||||
| 	*BaseCommand | 	*BaseCommand | ||||||
| } | } | ||||||
| @@ -23,20 +21,9 @@ func (c *PolicyListCommand) Synopsis() string { | |||||||
|  |  | ||||||
| func (c *PolicyListCommand) Help() string { | func (c *PolicyListCommand) Help() string { | ||||||
| 	helpText := ` | 	helpText := ` | ||||||
| Usage: vault policies [options] [NAME] | Usage: vault policy list [options] | ||||||
|  |  | ||||||
|   Lists the policies that are installed on the Vault server. If the optional |   Lists the names of the policies that are installed on the Vault server. | ||||||
|   argument is given, this command returns the policy's contents. |  | ||||||
|  |  | ||||||
|   List all policies stored in Vault: |  | ||||||
|  |  | ||||||
|       $ vault policies |  | ||||||
|  |  | ||||||
|   Read the contents of the policy named "my-policy": |  | ||||||
|  |  | ||||||
|       $ vault policies my-policy |  | ||||||
|  |  | ||||||
|   For a full list of examples, please see the documentation. |  | ||||||
|  |  | ||||||
| ` + c.Flags().Help() | ` + c.Flags().Help() | ||||||
|  |  | ||||||
| @@ -48,7 +35,7 @@ func (c *PolicyListCommand) Flags() *FlagSets { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (c *PolicyListCommand) AutocompleteArgs() complete.Predictor { | func (c *PolicyListCommand) AutocompleteArgs() complete.Predictor { | ||||||
| 	return c.PredictVaultPolicies() | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *PolicyListCommand) AutocompleteFlags() complete.Flags { | func (c *PolicyListCommand) AutocompleteFlags() complete.Flags { | ||||||
| @@ -64,10 +51,9 @@ func (c *PolicyListCommand) Run(args []string) int { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	args = f.Args() | 	args = f.Args() | ||||||
| 	switch len(args) { | 	switch { | ||||||
| 	case 0, 1: | 	case len(args) > 0: | ||||||
| 	default: | 		c.UI.Error(fmt.Sprintf("Too many arguments (expected 0, got %d)", len(args))) | ||||||
| 		c.UI.Error(fmt.Sprintf("Too many arguments (expected 0-2, got %d)", len(args))) |  | ||||||
| 		return 1 | 		return 1 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -77,28 +63,13 @@ func (c *PolicyListCommand) Run(args []string) int { | |||||||
| 		return 2 | 		return 2 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	switch len(args) { | 	policies, err := client.Sys().ListPolicies() | ||||||
| 	case 0: | 	if err != nil { | ||||||
| 		policies, err := client.Sys().ListPolicies() | 		c.UI.Error(fmt.Sprintf("Error listing policies: %s", err)) | ||||||
| 		if err != nil { | 		return 2 | ||||||
| 			c.UI.Error(fmt.Sprintf("Error listing policies: %s", err)) | 	} | ||||||
| 			return 2 | 	for _, p := range policies { | ||||||
| 		} | 		c.UI.Output(p) | ||||||
| 		for _, p := range policies { |  | ||||||
| 			c.UI.Output(p) |  | ||||||
| 		} |  | ||||||
| 	case 1: |  | ||||||
| 		name := strings.ToLower(strings.TrimSpace(args[0])) |  | ||||||
| 		rules, err := client.Sys().GetPolicy(name) |  | ||||||
| 		if err != nil { |  | ||||||
| 			c.UI.Error(fmt.Sprintf("Error reading policy %s: %s", name, err)) |  | ||||||
| 			return 2 |  | ||||||
| 		} |  | ||||||
| 		if rules == "" { |  | ||||||
| 			c.UI.Error(fmt.Sprintf("Error reading policy: no policy named: %s", name)) |  | ||||||
| 			return 2 |  | ||||||
| 		} |  | ||||||
| 		c.UI.Output(strings.TrimSpace(rules)) |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return 0 | 	return 0 | ||||||
|   | |||||||
| @@ -29,16 +29,10 @@ func TestPolicyListCommand_Run(t *testing.T) { | |||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			"too_many_args", | 			"too_many_args", | ||||||
| 			[]string{"foo", "bar"}, | 			[]string{"foo"}, | ||||||
| 			"Too many arguments", | 			"Too many arguments", | ||||||
| 			1, | 			1, | ||||||
| 		}, | 		}, | ||||||
| 		{ |  | ||||||
| 			"no_policy_exists", |  | ||||||
| 			[]string{"not-a-real-policy"}, |  | ||||||
| 			"no policy named", |  | ||||||
| 			2, |  | ||||||
| 		}, |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	t.Run("validations", func(t *testing.T) { | 	t.Run("validations", func(t *testing.T) { | ||||||
| @@ -69,7 +63,7 @@ func TestPolicyListCommand_Run(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("list", func(t *testing.T) { | 	t.Run("default", func(t *testing.T) { | ||||||
| 		t.Parallel() | 		t.Parallel() | ||||||
|  |  | ||||||
| 		client, closer := testVaultServer(t) | 		client, closer := testVaultServer(t) | ||||||
| @@ -90,33 +84,6 @@ func TestPolicyListCommand_Run(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("read", func(t *testing.T) { |  | ||||||
| 		t.Parallel() |  | ||||||
|  |  | ||||||
| 		client, closer := testVaultServer(t) |  | ||||||
| 		defer closer() |  | ||||||
|  |  | ||||||
| 		policy := `path "secret/" {}` |  | ||||||
| 		if err := client.Sys().PutPolicy("my-policy", policy); err != nil { |  | ||||||
| 			t.Fatal(err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		ui, cmd := testPolicyListCommand(t) |  | ||||||
| 		cmd.client = client |  | ||||||
|  |  | ||||||
| 		code := cmd.Run([]string{ |  | ||||||
| 			"my-policy", |  | ||||||
| 		}) |  | ||||||
| 		if exp := 0; code != exp { |  | ||||||
| 			t.Errorf("expected %d to be %d", code, exp) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		combined := ui.OutputWriter.String() + ui.ErrorWriter.String() |  | ||||||
| 		if !strings.Contains(combined, policy) { |  | ||||||
| 			t.Errorf("expected %q to contain %q", combined, policy) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	t.Run("communication_failure", func(t *testing.T) { | 	t.Run("communication_failure", func(t *testing.T) { | ||||||
| 		t.Parallel() | 		t.Parallel() | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										87
									
								
								command/policy_read.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								command/policy_read.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/mitchellh/cli" | ||||||
|  | 	"github.com/posener/complete" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var _ cli.Command = (*PolicyReadCommand)(nil) | ||||||
|  | var _ cli.CommandAutocomplete = (*PolicyReadCommand)(nil) | ||||||
|  |  | ||||||
|  | type PolicyReadCommand struct { | ||||||
|  | 	*BaseCommand | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyReadCommand) Synopsis() string { | ||||||
|  | 	return "Prints the contents of a policy" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyReadCommand) Help() string { | ||||||
|  | 	helpText := ` | ||||||
|  | Usage: vault policy read [options] [NAME] | ||||||
|  |  | ||||||
|  |   Prints the contents and metadata of the Vault policy named NAME. If the policy | ||||||
|  |   does not exist, an error is returned. | ||||||
|  |  | ||||||
|  |   Read the policy named "my-policy": | ||||||
|  |  | ||||||
|  |       $ vault policy read my-policy | ||||||
|  |  | ||||||
|  | ` + c.Flags().Help() | ||||||
|  |  | ||||||
|  | 	return strings.TrimSpace(helpText) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyReadCommand) Flags() *FlagSets { | ||||||
|  | 	return c.flagSet(FlagSetHTTP) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyReadCommand) AutocompleteArgs() complete.Predictor { | ||||||
|  | 	return c.PredictVaultPolicies() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyReadCommand) AutocompleteFlags() complete.Flags { | ||||||
|  | 	return c.Flags().Completions() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *PolicyReadCommand) Run(args []string) int { | ||||||
|  | 	f := c.Flags() | ||||||
|  |  | ||||||
|  | 	if err := f.Parse(args); err != nil { | ||||||
|  | 		c.UI.Error(err.Error()) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	args = f.Args() | ||||||
|  | 	switch { | ||||||
|  | 	case len(args) < 1: | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args))) | ||||||
|  | 		return 1 | ||||||
|  | 	case len(args) > 1: | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args))) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	client, err := c.Client() | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.UI.Error(err.Error()) | ||||||
|  | 		return 2 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	name := strings.ToLower(strings.TrimSpace(args[0])) | ||||||
|  | 	rules, err := client.Sys().GetPolicy(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Error reading policy named %s: %s", name, err)) | ||||||
|  | 		return 2 | ||||||
|  | 	} | ||||||
|  | 	if rules == "" { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("No policy named: %s", name)) | ||||||
|  | 		return 2 | ||||||
|  | 	} | ||||||
|  | 	c.UI.Output(strings.TrimSpace(rules)) | ||||||
|  |  | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
							
								
								
									
										128
									
								
								command/policy_read_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								command/policy_read_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | |||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/mitchellh/cli" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func testPolicyReadCommand(tb testing.TB) (*cli.MockUi, *PolicyReadCommand) { | ||||||
|  | 	tb.Helper() | ||||||
|  |  | ||||||
|  | 	ui := cli.NewMockUi() | ||||||
|  | 	return ui, &PolicyReadCommand{ | ||||||
|  | 		BaseCommand: &BaseCommand{ | ||||||
|  | 			UI: ui, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPolicyReadCommand_Run(t *testing.T) { | ||||||
|  | 	t.Parallel() | ||||||
|  |  | ||||||
|  | 	cases := []struct { | ||||||
|  | 		name string | ||||||
|  | 		args []string | ||||||
|  | 		out  string | ||||||
|  | 		code int | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			"too_many_args", | ||||||
|  | 			[]string{"foo", "bar"}, | ||||||
|  | 			"Too many arguments", | ||||||
|  | 			1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"no_policy_exists", | ||||||
|  | 			[]string{"not-a-real-policy"}, | ||||||
|  | 			"No policy named", | ||||||
|  | 			2, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	t.Run("validations", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		for _, tc := range cases { | ||||||
|  | 			tc := tc | ||||||
|  |  | ||||||
|  | 			t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 				t.Parallel() | ||||||
|  |  | ||||||
|  | 				client, closer := testVaultServer(t) | ||||||
|  | 				defer closer() | ||||||
|  |  | ||||||
|  | 				ui, cmd := testPolicyReadCommand(t) | ||||||
|  | 				cmd.client = client | ||||||
|  |  | ||||||
|  | 				code := cmd.Run(tc.args) | ||||||
|  | 				if code != tc.code { | ||||||
|  | 					t.Errorf("expected %d to be %d", code, tc.code) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				combined := ui.OutputWriter.String() + ui.ErrorWriter.String() | ||||||
|  | 				if !strings.Contains(combined, tc.out) { | ||||||
|  | 					t.Errorf("expected %q to contain %q", combined, tc.out) | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("default", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		client, closer := testVaultServer(t) | ||||||
|  | 		defer closer() | ||||||
|  |  | ||||||
|  | 		policy := `path "secret/" {}` | ||||||
|  | 		if err := client.Sys().PutPolicy("my-policy", policy); err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		ui, cmd := testPolicyReadCommand(t) | ||||||
|  | 		cmd.client = client | ||||||
|  |  | ||||||
|  | 		code := cmd.Run([]string{ | ||||||
|  | 			"my-policy", | ||||||
|  | 		}) | ||||||
|  | 		if exp := 0; code != exp { | ||||||
|  | 			t.Errorf("expected %d to be %d", code, exp) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		combined := ui.OutputWriter.String() + ui.ErrorWriter.String() | ||||||
|  | 		if !strings.Contains(combined, policy) { | ||||||
|  | 			t.Errorf("expected %q to contain %q", combined, policy) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("communication_failure", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		client, closer := testVaultServerBad(t) | ||||||
|  | 		defer closer() | ||||||
|  |  | ||||||
|  | 		ui, cmd := testPolicyReadCommand(t) | ||||||
|  | 		cmd.client = client | ||||||
|  |  | ||||||
|  | 		code := cmd.Run([]string{ | ||||||
|  | 			"my-policy", | ||||||
|  | 		}) | ||||||
|  | 		if exp := 2; code != exp { | ||||||
|  | 			t.Errorf("expected %d to be %d", code, exp) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		expected := "Error reading policy named my-policy: " | ||||||
|  | 		combined := ui.OutputWriter.String() + ui.ErrorWriter.String() | ||||||
|  | 		if !strings.Contains(combined, expected) { | ||||||
|  | 			t.Errorf("expected %q to contain %q", combined, expected) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("no_tabs", func(t *testing.T) { | ||||||
|  | 		t.Parallel() | ||||||
|  |  | ||||||
|  | 		_, cmd := testPolicyReadCommand(t) | ||||||
|  | 		assertNoTabs(t, cmd) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @@ -11,11 +11,9 @@ import ( | |||||||
| 	"github.com/posener/complete" | 	"github.com/posener/complete" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Ensure we are implementing the right interfaces. |  | ||||||
| var _ cli.Command = (*PolicyWriteCommand)(nil) | var _ cli.Command = (*PolicyWriteCommand)(nil) | ||||||
| var _ cli.CommandAutocomplete = (*PolicyWriteCommand)(nil) | var _ cli.CommandAutocomplete = (*PolicyWriteCommand)(nil) | ||||||
|  |  | ||||||
| // PolicyWriteCommand is a Command uploads a policy |  | ||||||
| type PolicyWriteCommand struct { | type PolicyWriteCommand struct { | ||||||
| 	*BaseCommand | 	*BaseCommand | ||||||
|  |  | ||||||
| @@ -23,26 +21,24 @@ type PolicyWriteCommand struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (c *PolicyWriteCommand) Synopsis() string { | func (c *PolicyWriteCommand) Synopsis() string { | ||||||
| 	return "Uploads a policy file" | 	return "Uploads a named policy from a file" | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *PolicyWriteCommand) Help() string { | func (c *PolicyWriteCommand) Help() string { | ||||||
| 	helpText := ` | 	helpText := ` | ||||||
| Usage: vault policy-write [options] NAME PATH | Usage: vault policy write [options] NAME PATH | ||||||
|  |  | ||||||
|   Uploads a policy with the given name from the contents of a local file or |   Uploads a policy with name NAME from the contents of a local file PATH or | ||||||
|   stdin. If the path is "-", the policy is read from stdin. Otherwise, it is |   stdin. If PATH is "-", the policy is read from stdin. Otherwise, it is | ||||||
|   loaded from the file at the given path. |   loaded from the file at the given path on the local disk. | ||||||
|  |  | ||||||
|   Upload a policy named "my-policy" from /tmp/policy.hcl on the local disk: |   Upload a policy named "my-policy" from "/tmp/policy.hcl" on the local disk: | ||||||
|  |  | ||||||
|       $ vault policy-write my-policy /tmp/policy.hcl |       $ vault policy write my-policy /tmp/policy.hcl | ||||||
|  |  | ||||||
|   Upload a policy from stdin: |   Upload a policy from stdin: | ||||||
|  |  | ||||||
|       $ cat my-policy.hcl | vault policy-write my-policy - |       $ cat my-policy.hcl | vault policy write my-policy - | ||||||
|  |  | ||||||
|   For a full list of examples, please see the documentation. |  | ||||||
|  |  | ||||||
| ` + c.Flags().Help() | ` + c.Flags().Help() | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Seth Vargo
					Seth Vargo