mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	cli: Add 'agent generate-config' sub-command (#20530)
This commit is contained in:
		 Anton Averchenkov
					Anton Averchenkov
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							3d7d8f4965
						
					
				
				
					commit
					1a1af69cdd
				
			
							
								
								
									
										3
									
								
								changelog/20530.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/20530.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | ```release-note:feature | ||||||
|  | cli: Add 'agent generate-config' sub-command | ||||||
|  | ``` | ||||||
							
								
								
									
										402
									
								
								command/agent_generate_config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										402
									
								
								command/agent_generate_config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,402 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	paths "path" | ||||||
|  | 	"sort" | ||||||
|  | 	"strings" | ||||||
|  | 	"unicode" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/hcl/v2/gohcl" | ||||||
|  | 	"github.com/hashicorp/hcl/v2/hclwrite" | ||||||
|  | 	"github.com/hashicorp/vault/api" | ||||||
|  | 	"github.com/mitchellh/cli" | ||||||
|  | 	"github.com/mitchellh/go-homedir" | ||||||
|  | 	"github.com/posener/complete" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	_ cli.Command             = (*AgentGenerateConfigCommand)(nil) | ||||||
|  | 	_ cli.CommandAutocomplete = (*AgentGenerateConfigCommand)(nil) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type AgentGenerateConfigCommand struct { | ||||||
|  | 	*BaseCommand | ||||||
|  |  | ||||||
|  | 	flagType  string | ||||||
|  | 	flagPaths []string | ||||||
|  | 	flagExec  string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *AgentGenerateConfigCommand) Synopsis() string { | ||||||
|  | 	return "Generate a Vault Agent configuration file." | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *AgentGenerateConfigCommand) Help() string { | ||||||
|  | 	helpText := ` | ||||||
|  | Usage: vault agent generate-config [options] [args] | ||||||
|  |  | ||||||
|  | ` + c.Flags().Help() | ||||||
|  |  | ||||||
|  | 	return strings.TrimSpace(helpText) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *AgentGenerateConfigCommand) Flags() *FlagSets { | ||||||
|  | 	set := NewFlagSets(c.UI) | ||||||
|  |  | ||||||
|  | 	// Common Options | ||||||
|  | 	f := set.NewFlagSet("Command Options") | ||||||
|  |  | ||||||
|  | 	f.StringVar(&StringVar{ | ||||||
|  | 		Name:   "type", | ||||||
|  | 		Target: &c.flagType, | ||||||
|  | 		Usage:  "Type of configuration file to generate; currently, only 'env-template' is supported.", | ||||||
|  | 		Completion: complete.PredictSet( | ||||||
|  | 			"env-template", | ||||||
|  | 		), | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	f.StringSliceVar(&StringSliceVar{ | ||||||
|  | 		Name:       "path", | ||||||
|  | 		Target:     &c.flagPaths, | ||||||
|  | 		Usage:      "Path to a kv-v1 or kv-v2 secret (e.g. secret/data/foo, kv-v2/prefix/*); multiple secrets and tail '*' wildcards are allowed.", | ||||||
|  | 		Completion: c.PredictVaultFolders(), | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	f.StringVar(&StringVar{ | ||||||
|  | 		Name:    "exec", | ||||||
|  | 		Target:  &c.flagExec, | ||||||
|  | 		Default: "env", | ||||||
|  | 		Usage:   "The command to execute in env-template mode.", | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	return set | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *AgentGenerateConfigCommand) AutocompleteArgs() complete.Predictor { | ||||||
|  | 	return complete.PredictNothing | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *AgentGenerateConfigCommand) AutocompleteFlags() complete.Flags { | ||||||
|  | 	return c.Flags().Completions() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *AgentGenerateConfigCommand) Run(args []string) int { | ||||||
|  | 	flags := c.Flags() | ||||||
|  |  | ||||||
|  | 	if err := flags.Parse(args); err != nil { | ||||||
|  | 		c.UI.Error(err.Error()) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	args = flags.Args() | ||||||
|  |  | ||||||
|  | 	if len(args) > 1 { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Too many arguments (expected at most 1, got %d)", len(args))) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if c.flagType == "" { | ||||||
|  | 		c.UI.Error(`Please specify a -type flag; currently only -type="env-template" is supported.`) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if c.flagType != "env-template" { | ||||||
|  | 		c.UI.Error(fmt.Sprintf(`%q is not a supported configuration type; currently only -type="env-template" is supported.`, c.flagType)) | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	client, err := c.Client() | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.UI.Error(err.Error()) | ||||||
|  | 		return 2 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	config, err := generateConfiguration(context.Background(), client, c.flagExec, c.flagPaths) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Error: %v", err)) | ||||||
|  | 		return 2 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var configPath string | ||||||
|  | 	if len(args) == 1 { | ||||||
|  | 		configPath = args[0] | ||||||
|  | 	} else { | ||||||
|  | 		configPath = "agent.hcl" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	f, err := os.Create(configPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Could not create configuration file %q: %v", configPath, err)) | ||||||
|  | 		return 3 | ||||||
|  | 	} | ||||||
|  | 	defer func() { | ||||||
|  | 		if err := f.Close(); err != nil { | ||||||
|  | 			c.UI.Error(fmt.Sprintf("Could not close configuration file %q: %v", configPath, err)) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	if _, err := config.WriteTo(f); err != nil { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Could not write to configuration file %q: %v", configPath, err)) | ||||||
|  | 		return 3 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.UI.Info(fmt.Sprintf("Successfully generated %q configuration file!", configPath)) | ||||||
|  |  | ||||||
|  | 	c.UI.Warn("Warning: the generated file uses 'token_file' authentication method, which is not suitable for production environments.") | ||||||
|  |  | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func generateConfiguration(ctx context.Context, client *api.Client, flagExec string, flagPaths []string) (io.WriterTo, error) { | ||||||
|  | 	var execCommand []string | ||||||
|  | 	if flagExec != "" { | ||||||
|  | 		execCommand = strings.Split(flagExec, " ") | ||||||
|  | 	} else { | ||||||
|  | 		execCommand = []string{"env"} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tokenPath, err := homedir.Expand("~/.vault-token") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("could not expand home directory: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	templates, err := constructTemplates(ctx, client, flagPaths) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("could not generate templates: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	config := generatedConfig{ | ||||||
|  | 		AutoAuth: generatedConfigAutoAuth{ | ||||||
|  | 			Method: generatedConfigAutoAuthMethod{ | ||||||
|  | 				Type: "token_file", | ||||||
|  | 				Config: generatedConfigAutoAuthMethodConfig{ | ||||||
|  | 					TokenFilePath: tokenPath, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		TemplateConfig: generatedConfigTemplateConfig{ | ||||||
|  | 			StaticSecretRenderInterval: "5m", | ||||||
|  | 			ExitOnRetryFailure:         true, | ||||||
|  | 		}, | ||||||
|  | 		Vault: generatedConfigVault{ | ||||||
|  | 			Address: client.Address(), | ||||||
|  | 		}, | ||||||
|  | 		Exec: generatedConfigExec{ | ||||||
|  | 			Command:                execCommand, | ||||||
|  | 			RestartOnSecretChanges: "always", | ||||||
|  | 			RestartStopSignal:      "SIGTERM", | ||||||
|  | 		}, | ||||||
|  | 		EnvTemplates: templates, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	contents := hclwrite.NewEmptyFile() | ||||||
|  |  | ||||||
|  | 	gohcl.EncodeIntoBody(&config, contents.Body()) | ||||||
|  |  | ||||||
|  | 	return contents, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func constructTemplates(ctx context.Context, client *api.Client, paths []string) ([]generatedConfigEnvTemplate, error) { | ||||||
|  | 	var templates []generatedConfigEnvTemplate | ||||||
|  |  | ||||||
|  | 	for _, path := range paths { | ||||||
|  | 		path = sanitizePath(path) | ||||||
|  |  | ||||||
|  | 		mountPath, v2, err := isKVv2(path, client) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("could not validate secret path %q: %w", path, err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		switch { | ||||||
|  | 		case strings.HasSuffix(path, "/*"): | ||||||
|  | 			// this path contains a tail wildcard, attempt to walk the tree | ||||||
|  | 			t, err := constructTemplatesFromTree(ctx, client, path[:len(path)-2], mountPath, v2) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("could not traverse sercet at %q: %w", path, err) | ||||||
|  | 			} | ||||||
|  | 			templates = append(templates, t...) | ||||||
|  |  | ||||||
|  | 		case strings.Contains(path, "*"): | ||||||
|  | 			// don't allow any other wildcards | ||||||
|  | 			return nil, fmt.Errorf("the path %q cannot contain '*' wildcard characters except as the last element of the path", path) | ||||||
|  |  | ||||||
|  | 		default: | ||||||
|  | 			// regular secret path | ||||||
|  | 			t, err := constructTemplatesFromSecret(ctx, client, path, mountPath, v2) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("could not read secret at %q: %v", path, err) | ||||||
|  | 			} | ||||||
|  | 			templates = append(templates, t...) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return templates, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func constructTemplatesFromTree(ctx context.Context, client *api.Client, path, mountPath string, v2 bool) ([]generatedConfigEnvTemplate, error) { | ||||||
|  | 	var templates []generatedConfigEnvTemplate | ||||||
|  |  | ||||||
|  | 	if v2 { | ||||||
|  | 		metadataPath := strings.Replace( | ||||||
|  | 			path, | ||||||
|  | 			paths.Join(mountPath, "data"), | ||||||
|  | 			paths.Join(mountPath, "metadata"), | ||||||
|  | 			1, | ||||||
|  | 		) | ||||||
|  | 		if path != metadataPath { | ||||||
|  | 			path = metadataPath | ||||||
|  | 		} else { | ||||||
|  | 			path = addPrefixToKVPath(path, mountPath, "metadata", true) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err := walkSecretsTree(ctx, client, path, func(child string, directory bool) error { | ||||||
|  | 		if directory { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		dataPath := strings.Replace( | ||||||
|  | 			child, | ||||||
|  | 			paths.Join(mountPath, "metadata"), | ||||||
|  | 			paths.Join(mountPath, "data"), | ||||||
|  | 			1, | ||||||
|  | 		) | ||||||
|  |  | ||||||
|  | 		t, err := constructTemplatesFromSecret(ctx, client, dataPath, mountPath, v2) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		templates = append(templates, t...) | ||||||
|  |  | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return templates, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func constructTemplatesFromSecret(ctx context.Context, client *api.Client, path, mountPath string, v2 bool) ([]generatedConfigEnvTemplate, error) { | ||||||
|  | 	var templates []generatedConfigEnvTemplate | ||||||
|  |  | ||||||
|  | 	if v2 { | ||||||
|  | 		path = addPrefixToKVPath(path, mountPath, "data", true) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	resp, err := client.Logical().ReadWithContext(ctx, path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("error querying: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if resp == nil { | ||||||
|  | 		return nil, fmt.Errorf("secret not found") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var data map[string]interface{} | ||||||
|  | 	if v2 { | ||||||
|  | 		internal, ok := resp.Data["data"] | ||||||
|  | 		if !ok { | ||||||
|  | 			return nil, fmt.Errorf("secret.Data not found") | ||||||
|  | 		} | ||||||
|  | 		data = internal.(map[string]interface{}) | ||||||
|  | 	} else { | ||||||
|  | 		data = resp.Data | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fields := make([]string, 0, len(data)) | ||||||
|  |  | ||||||
|  | 	for field := range data { | ||||||
|  | 		fields = append(fields, field) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// sort for a deterministic output | ||||||
|  | 	sort.Strings(fields) | ||||||
|  |  | ||||||
|  | 	var dataContents string | ||||||
|  | 	if v2 { | ||||||
|  | 		dataContents = ".Data.data" | ||||||
|  | 	} else { | ||||||
|  | 		dataContents = ".Data" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, field := range fields { | ||||||
|  | 		templates = append(templates, generatedConfigEnvTemplate{ | ||||||
|  | 			Name:              constructDefaultEnvironmentKey(path, field), | ||||||
|  | 			Contents:          fmt.Sprintf(`{{ with secret "%s" }}{{ %s.%s }}{{ end }}`, path, dataContents, field), | ||||||
|  | 			ErrorOnMissingKey: true, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return templates, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func constructDefaultEnvironmentKey(path string, field string) string { | ||||||
|  | 	pathParts := strings.Split(path, "/") | ||||||
|  | 	pathPartsLast := pathParts[len(pathParts)-1] | ||||||
|  |  | ||||||
|  | 	notLetterOrNumber := func(r rune) bool { | ||||||
|  | 		return !unicode.IsLetter(r) && !unicode.IsNumber(r) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	p1 := strings.FieldsFunc(pathPartsLast, notLetterOrNumber) | ||||||
|  | 	p2 := strings.FieldsFunc(field, notLetterOrNumber) | ||||||
|  |  | ||||||
|  | 	keyParts := append(p1, p2...) | ||||||
|  |  | ||||||
|  | 	return strings.ToUpper(strings.Join(keyParts, "_")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Below, we are redefining a subset of the configuration-related structures | ||||||
|  | // defined under command/agent/config. Using these structures we can tailor the | ||||||
|  | // output of the generated config, while using the original structures would | ||||||
|  | // have produced an HCL document with many empty fields. The structures below | ||||||
|  | // should not be used for anything other than generation. | ||||||
|  |  | ||||||
|  | type generatedConfig struct { | ||||||
|  | 	AutoAuth       generatedConfigAutoAuth       `hcl:"auto_auth,block"` | ||||||
|  | 	TemplateConfig generatedConfigTemplateConfig `hcl:"template_config,block"` | ||||||
|  | 	Vault          generatedConfigVault          `hcl:"vault,block"` | ||||||
|  | 	EnvTemplates   []generatedConfigEnvTemplate  `hcl:"env_template,block"` | ||||||
|  | 	Exec           generatedConfigExec           `hcl:"exec,block"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type generatedConfigTemplateConfig struct { | ||||||
|  | 	StaticSecretRenderInterval string `hcl:"static_secret_render_interval"` | ||||||
|  | 	ExitOnRetryFailure         bool   `hcl:"exit_on_retry_failure"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type generatedConfigExec struct { | ||||||
|  | 	Command                []string `hcl:"command"` | ||||||
|  | 	RestartOnSecretChanges string   `hcl:"restart_on_secret_changes"` | ||||||
|  | 	RestartStopSignal      string   `hcl:"restart_stop_signal"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type generatedConfigEnvTemplate struct { | ||||||
|  | 	Name              string `hcl:"name,label"` | ||||||
|  | 	Contents          string `hcl:"contents,attr"` | ||||||
|  | 	ErrorOnMissingKey bool   `hcl:"error_on_missing_key"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type generatedConfigVault struct { | ||||||
|  | 	Address string `hcl:"address"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type generatedConfigAutoAuth struct { | ||||||
|  | 	Method generatedConfigAutoAuthMethod `hcl:"method,block"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type generatedConfigAutoAuthMethod struct { | ||||||
|  | 	Type   string                              `hcl:"type"` | ||||||
|  | 	Config generatedConfigAutoAuthMethodConfig `hcl:"config,block"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type generatedConfigAutoAuthMethodConfig struct { | ||||||
|  | 	TokenFilePath string `hcl:"token_file_path"` | ||||||
|  | } | ||||||
							
								
								
									
										274
									
								
								command/agent_generate_config_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								command/agent_generate_config_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,274 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  |  | ||||||
|  | package command | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"reflect" | ||||||
|  | 	"regexp" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // TestConstructTemplates tests the construcTemplates helper function | ||||||
|  | func TestConstructTemplates(t *testing.T) { | ||||||
|  | 	ctx, cancelContextFunc := context.WithTimeout(context.Background(), 5*time.Second) | ||||||
|  | 	defer cancelContextFunc() | ||||||
|  |  | ||||||
|  | 	client, closer := testVaultServerWithSecrets(ctx, t) | ||||||
|  | 	defer closer() | ||||||
|  |  | ||||||
|  | 	cases := map[string]struct { | ||||||
|  | 		paths         []string | ||||||
|  | 		expected      []generatedConfigEnvTemplate | ||||||
|  | 		expectedError bool | ||||||
|  | 	}{ | ||||||
|  | 		"kv-v1-simple": { | ||||||
|  | 			paths: []string{"kv-v1/foo"}, | ||||||
|  | 			expected: []generatedConfigEnvTemplate{ | ||||||
|  | 				{Contents: `{{ with secret "kv-v1/foo" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v1/foo" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: false, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		"kv-v2-simple": { | ||||||
|  | 			paths: []string{"kv-v2/foo"}, | ||||||
|  | 			expected: []generatedConfigEnvTemplate{ | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: false, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		"kv-v2-data-in-path": { | ||||||
|  | 			paths: []string{"kv-v2/data/foo"}, | ||||||
|  | 			expected: []generatedConfigEnvTemplate{ | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: false, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		"kv-v1-nested": { | ||||||
|  | 			paths: []string{"kv-v1/app-1/*"}, | ||||||
|  | 			expected: []generatedConfigEnvTemplate{ | ||||||
|  | 				{Contents: `{{ with secret "kv-v1/app-1/bar" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v1/app-1/bar" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_USER"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v1/app-1/foo" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v1/app-1/foo" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v1/app-1/nested/baz" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAZ_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v1/app-1/nested/baz" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAZ_USER"}, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: false, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		"kv-v2-nested": { | ||||||
|  | 			paths: []string{"kv-v2/app-1/*"}, | ||||||
|  | 			expected: []generatedConfigEnvTemplate{ | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/app-1/bar" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/app-1/bar" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_USER"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/app-1/foo" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/app-1/foo" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/app-1/nested/baz" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAZ_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/app-1/nested/baz" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAZ_USER"}, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: false, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		"kv-v1-multi-path": { | ||||||
|  | 			paths: []string{"kv-v1/foo", "kv-v1/app-1/bar"}, | ||||||
|  | 			expected: []generatedConfigEnvTemplate{ | ||||||
|  | 				{Contents: `{{ with secret "kv-v1/foo" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v1/foo" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v1/app-1/bar" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v1/app-1/bar" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_USER"}, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: false, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		"kv-v2-multi-path": { | ||||||
|  | 			paths: []string{"kv-v2/foo", "kv-v2/app-1/bar"}, | ||||||
|  | 			expected: []generatedConfigEnvTemplate{ | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/app-1/bar" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_PASSWORD"}, | ||||||
|  | 				{Contents: `{{ with secret "kv-v2/data/app-1/bar" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_USER"}, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: false, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		"kv-v1-path-not-found": { | ||||||
|  | 			paths:         []string{"kv-v1/does/not/exist"}, | ||||||
|  | 			expected:      nil, | ||||||
|  | 			expectedError: true, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		"kv-v2-path-not-found": { | ||||||
|  | 			paths:         []string{"kv-v2/does/not/exist"}, | ||||||
|  | 			expected:      nil, | ||||||
|  | 			expectedError: true, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		"kv-v1-early-wildcard": { | ||||||
|  | 			paths:         []string{"kv-v1/*/foo"}, | ||||||
|  | 			expected:      nil, | ||||||
|  | 			expectedError: true, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		"kv-v2-early-wildcard": { | ||||||
|  | 			paths:         []string{"kv-v2/*/foo"}, | ||||||
|  | 			expected:      nil, | ||||||
|  | 			expectedError: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for name, tc := range cases { | ||||||
|  | 		name, tc := name, tc | ||||||
|  |  | ||||||
|  | 		t.Run(name, func(t *testing.T) { | ||||||
|  | 			templates, err := constructTemplates(ctx, client, tc.paths) | ||||||
|  |  | ||||||
|  | 			if tc.expectedError { | ||||||
|  | 				if err == nil { | ||||||
|  | 					t.Fatal("an error was expected but the test succeeded") | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				if err != nil { | ||||||
|  | 					t.Fatal(err) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if !reflect.DeepEqual(tc.expected, templates) { | ||||||
|  | 					t.Fatalf("unexpected output; want: %v, got: %v", tc.expected, templates) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TestGenerateConfiguration tests the generateConfiguration helper function | ||||||
|  | func TestGenerateConfiguration(t *testing.T) { | ||||||
|  | 	ctx, cancelContextFunc := context.WithTimeout(context.Background(), 5*time.Second) | ||||||
|  | 	defer cancelContextFunc() | ||||||
|  |  | ||||||
|  | 	client, closer := testVaultServerWithSecrets(ctx, t) | ||||||
|  | 	defer closer() | ||||||
|  |  | ||||||
|  | 	cases := map[string]struct { | ||||||
|  | 		flagExec      string | ||||||
|  | 		flagPaths     []string | ||||||
|  | 		expected      *regexp.Regexp | ||||||
|  | 		expectedError bool | ||||||
|  | 	}{ | ||||||
|  | 		"kv-v1-simple": { | ||||||
|  | 			flagExec:  "./my-app arg1 arg2", | ||||||
|  | 			flagPaths: []string{"kv-v1/foo"}, | ||||||
|  | 			expected: regexp.MustCompile(` | ||||||
|  | auto_auth \{ | ||||||
|  |  | ||||||
|  |   method \{ | ||||||
|  |     type = "token_file" | ||||||
|  |  | ||||||
|  |     config \{ | ||||||
|  |       token_file_path = ".*/.vault-token" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template_config \{ | ||||||
|  |   static_secret_render_interval = "5m" | ||||||
|  |   exit_on_retry_failure         = true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | vault \{ | ||||||
|  |   address = "https://127.0.0.1:[0-9]{5}" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | env_template "FOO_PASSWORD" \{ | ||||||
|  |   contents             = "\{\{ with secret \\"kv-v1/foo\\" }}\{\{ .Data.password }}\{\{ end }}" | ||||||
|  |   error_on_missing_key = true | ||||||
|  | } | ||||||
|  | env_template "FOO_USER" \{ | ||||||
|  |   contents             = "\{\{ with secret \\"kv-v1/foo\\" }}\{\{ .Data.user }}\{\{ end }}" | ||||||
|  |   error_on_missing_key = true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | exec \{ | ||||||
|  |   command                   = \["./my-app", "arg1", "arg2"\] | ||||||
|  |   restart_on_secret_changes = "always" | ||||||
|  |   restart_stop_signal       = "SIGTERM" | ||||||
|  | } | ||||||
|  | `), | ||||||
|  | 			expectedError: false, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		"kv-v2-default-exec": { | ||||||
|  | 			flagExec:  "", | ||||||
|  | 			flagPaths: []string{"kv-v2/foo"}, | ||||||
|  | 			expected: regexp.MustCompile(` | ||||||
|  | auto_auth \{ | ||||||
|  |  | ||||||
|  |   method \{ | ||||||
|  |     type = "token_file" | ||||||
|  |  | ||||||
|  |     config \{ | ||||||
|  |       token_file_path = ".*/.vault-token" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template_config \{ | ||||||
|  |   static_secret_render_interval = "5m" | ||||||
|  |   exit_on_retry_failure         = true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | vault \{ | ||||||
|  |   address = "https://127.0.0.1:[0-9]{5}" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | env_template "FOO_PASSWORD" \{ | ||||||
|  |   contents             = "\{\{ with secret \\"kv-v2/data/foo\\" }}\{\{ .Data.data.password }}\{\{ end }}" | ||||||
|  |   error_on_missing_key = true | ||||||
|  | } | ||||||
|  | env_template "FOO_USER" \{ | ||||||
|  |   contents             = "\{\{ with secret \\"kv-v2/data/foo\\" }}\{\{ .Data.data.user }}\{\{ end }}" | ||||||
|  |   error_on_missing_key = true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | exec \{ | ||||||
|  |   command                   = \["env"\] | ||||||
|  |   restart_on_secret_changes = "always" | ||||||
|  |   restart_stop_signal       = "SIGTERM" | ||||||
|  | } | ||||||
|  | `), | ||||||
|  | 			expectedError: false, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for name, tc := range cases { | ||||||
|  | 		name, tc := name, tc | ||||||
|  |  | ||||||
|  | 		t.Run(name, func(t *testing.T) { | ||||||
|  | 			var config bytes.Buffer | ||||||
|  |  | ||||||
|  | 			c, err := generateConfiguration(ctx, client, tc.flagExec, tc.flagPaths) | ||||||
|  | 			c.WriteTo(&config) | ||||||
|  |  | ||||||
|  | 			if tc.expectedError { | ||||||
|  | 				if err == nil { | ||||||
|  | 					t.Fatal("an error was expected but the test succeeded") | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				if err != nil { | ||||||
|  | 					t.Fatal(err) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if !tc.expected.MatchString(config.String()) { | ||||||
|  | 					t.Fatalf("unexpected output; want: %v, got: %v", tc.expected.String(), config.String()) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -71,6 +71,50 @@ func testVaultServer(tb testing.TB) (*api.Client, func()) { | |||||||
| 	return client, closer | 	return client, closer | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func testVaultServerWithSecrets(ctx context.Context, tb testing.TB) (*api.Client, func()) { | ||||||
|  | 	tb.Helper() | ||||||
|  |  | ||||||
|  | 	client, _, closer := testVaultServerUnseal(tb) | ||||||
|  |  | ||||||
|  | 	// enable kv-v1 backend | ||||||
|  | 	if err := client.Sys().Mount("kv-v1/", &api.MountInput{ | ||||||
|  | 		Type: "kv-v1", | ||||||
|  | 	}); err != nil { | ||||||
|  | 		tb.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// enable kv-v2 backend | ||||||
|  | 	if err := client.Sys().Mount("kv-v2/", &api.MountInput{ | ||||||
|  | 		Type: "kv-v2", | ||||||
|  | 	}); err != nil { | ||||||
|  | 		tb.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// populate dummy secrets | ||||||
|  | 	for _, path := range []string{ | ||||||
|  | 		"foo", | ||||||
|  | 		"app-1/foo", | ||||||
|  | 		"app-1/bar", | ||||||
|  | 		"app-1/nested/baz", | ||||||
|  | 	} { | ||||||
|  | 		if err := client.KVv1("kv-v1").Put(ctx, path, map[string]interface{}{ | ||||||
|  | 			"user":     "test", | ||||||
|  | 			"password": "Hashi123", | ||||||
|  | 		}); err != nil { | ||||||
|  | 			tb.Fatal(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if _, err := client.KVv2("kv-v2").Put(ctx, path, map[string]interface{}{ | ||||||
|  | 			"user":     "test", | ||||||
|  | 			"password": "Hashi123", | ||||||
|  | 		}); err != nil { | ||||||
|  | 			tb.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return client, closer | ||||||
|  | } | ||||||
|  |  | ||||||
| func testVaultServerWithKVVersion(tb testing.TB, kvVersion string) (*api.Client, func()) { | func testVaultServerWithKVVersion(tb testing.TB, kvVersion string) (*api.Client, func()) { | ||||||
| 	tb.Helper() | 	tb.Helper() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -268,6 +268,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) map[string]cli.Co | |||||||
| 				SighupCh:   MakeSighupCh(), | 				SighupCh:   MakeSighupCh(), | ||||||
| 			}, nil | 			}, nil | ||||||
| 		}, | 		}, | ||||||
|  | 		"agent generate-config": func() (cli.Command, error) { | ||||||
|  | 			return &AgentGenerateConfigCommand{ | ||||||
|  | 				BaseCommand: getBaseCommand(), | ||||||
|  | 			}, nil | ||||||
|  | 		}, | ||||||
| 		"audit": func() (cli.Command, error) { | 		"audit": func() (cli.Command, error) { | ||||||
| 			return &AuditCommand{ | 			return &AuditCommand{ | ||||||
| 				BaseCommand: getBaseCommand(), | 				BaseCommand: getBaseCommand(), | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							| @@ -108,6 +108,7 @@ require ( | |||||||
| 	github.com/hashicorp/go-version v1.6.0 | 	github.com/hashicorp/go-version v1.6.0 | ||||||
| 	github.com/hashicorp/golang-lru v0.5.4 | 	github.com/hashicorp/golang-lru v0.5.4 | ||||||
| 	github.com/hashicorp/hcl v1.0.1-vault-5 | 	github.com/hashicorp/hcl v1.0.1-vault-5 | ||||||
|  | 	github.com/hashicorp/hcl/v2 v2.16.2 | ||||||
| 	github.com/hashicorp/hcp-link v0.1.0 | 	github.com/hashicorp/hcp-link v0.1.0 | ||||||
| 	github.com/hashicorp/hcp-scada-provider v0.2.1 | 	github.com/hashicorp/hcp-scada-provider v0.2.1 | ||||||
| 	github.com/hashicorp/hcp-sdk-go v0.23.0 | 	github.com/hashicorp/hcp-sdk-go v0.23.0 | ||||||
| @@ -258,8 +259,10 @@ require ( | |||||||
| 	github.com/Masterminds/sprig/v3 v3.2.3 // indirect | 	github.com/Masterminds/sprig/v3 v3.2.3 // indirect | ||||||
| 	github.com/Microsoft/go-winio v0.6.1 // indirect | 	github.com/Microsoft/go-winio v0.6.1 // indirect | ||||||
| 	github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect | 	github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect | ||||||
|  | 	github.com/agext/levenshtein v1.2.1 // indirect | ||||||
| 	github.com/andybalholm/brotli v1.0.4 // indirect | 	github.com/andybalholm/brotli v1.0.4 // indirect | ||||||
| 	github.com/apache/arrow/go/arrow v0.0.0-20210818145353-234c94e4ce64 // indirect | 	github.com/apache/arrow/go/arrow v0.0.0-20210818145353-234c94e4ce64 // indirect | ||||||
|  | 	github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2 v1.8.0 // indirect | 	github.com/aws/aws-sdk-go-v2 v1.8.0 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/credentials v1.3.2 // indirect | 	github.com/aws/aws-sdk-go-v2/credentials v1.3.2 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.4.0 // indirect | 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.4.0 // indirect | ||||||
| @@ -451,6 +454,7 @@ require ( | |||||||
| 	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect | 	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect | ||||||
| 	github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect | 	github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect | ||||||
| 	github.com/yusufpapurcu/wmi v1.2.2 // indirect | 	github.com/yusufpapurcu/wmi v1.2.2 // indirect | ||||||
|  | 	github.com/zclconf/go-cty v1.12.1 // indirect | ||||||
| 	go.etcd.io/etcd/api/v3 v3.5.7 // indirect | 	go.etcd.io/etcd/api/v3 v3.5.7 // indirect | ||||||
| 	go.opencensus.io v0.24.0 // indirect | 	go.opencensus.io v0.24.0 // indirect | ||||||
| 	go.uber.org/multierr v1.7.0 // indirect | 	go.uber.org/multierr v1.7.0 // indirect | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								go.sum
									
									
									
									
									
								
							| @@ -689,6 +689,8 @@ github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KM | |||||||
| github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= | github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= | ||||||
| github.com/aerospike/aerospike-client-go/v5 v5.6.0 h1:tRxcUq0HY8fFPQEzF3EgrknF+w1xFO0YDfUb9Nm8yRI= | github.com/aerospike/aerospike-client-go/v5 v5.6.0 h1:tRxcUq0HY8fFPQEzF3EgrknF+w1xFO0YDfUb9Nm8yRI= | ||||||
| github.com/aerospike/aerospike-client-go/v5 v5.6.0/go.mod h1:rJ/KpmClE7kiBPfvAPrGw9WuNOiz8v2uKbQaUyYPXtI= | github.com/aerospike/aerospike-client-go/v5 v5.6.0/go.mod h1:rJ/KpmClE7kiBPfvAPrGw9WuNOiz8v2uKbQaUyYPXtI= | ||||||
|  | github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= | ||||||
|  | github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= | ||||||
| github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= | github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= | ||||||
| github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= | ||||||
| github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= | github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= | ||||||
| @@ -717,6 +719,8 @@ github.com/apache/arrow/go/arrow v0.0.0-20210818145353-234c94e4ce64 h1:ZsPrlYPY/ | |||||||
| github.com/apache/arrow/go/arrow v0.0.0-20210818145353-234c94e4ce64/go.mod h1:2qMFB56yOP3KzkB3PbYZ4AlUFg3a88F67TIx5lB/WwY= | github.com/apache/arrow/go/arrow v0.0.0-20210818145353-234c94e4ce64/go.mod h1:2qMFB56yOP3KzkB3PbYZ4AlUFg3a88F67TIx5lB/WwY= | ||||||
| github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= | github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= | ||||||
| github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= | github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= | ||||||
|  | github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= | ||||||
|  | github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= | ||||||
| github.com/apple/foundationdb/bindings/go v0.0.0-20190411004307-cd5c9d91fad2 h1:VoHKYIXEQU5LWoambPBOvYxyLqZYHuj+rj5DVnMUc3k= | github.com/apple/foundationdb/bindings/go v0.0.0-20190411004307-cd5c9d91fad2 h1:VoHKYIXEQU5LWoambPBOvYxyLqZYHuj+rj5DVnMUc3k= | ||||||
| github.com/apple/foundationdb/bindings/go v0.0.0-20190411004307-cd5c9d91fad2/go.mod h1:OMVSB21p9+xQUIqlGizHPZfjK+SHws1ht+ZytVDoz9U= | github.com/apple/foundationdb/bindings/go v0.0.0-20190411004307-cd5c9d91fad2/go.mod h1:OMVSB21p9+xQUIqlGizHPZfjK+SHws1ht+ZytVDoz9U= | ||||||
| github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= | ||||||
| @@ -1750,6 +1754,8 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG | |||||||
| github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | ||||||
| github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= | github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= | ||||||
| github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= | github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= | ||||||
|  | github.com/hashicorp/hcl/v2 v2.16.2 h1:mpkHZh/Tv+xet3sy3F9Ld4FyI2tUpWe9x3XtPx9f1a0= | ||||||
|  | github.com/hashicorp/hcl/v2 v2.16.2/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng= | ||||||
| github.com/hashicorp/hcp-link v0.1.0 h1:F6F1cpADc+o5EBI5CbJn5RX4qdFSLpuA4fN69eeE5lQ= | github.com/hashicorp/hcp-link v0.1.0 h1:F6F1cpADc+o5EBI5CbJn5RX4qdFSLpuA4fN69eeE5lQ= | ||||||
| github.com/hashicorp/hcp-link v0.1.0/go.mod h1:BWVDuJDHrKJtWc5qI07bX5xlLjSgWq6kYLQUeG1g5dM= | github.com/hashicorp/hcp-link v0.1.0/go.mod h1:BWVDuJDHrKJtWc5qI07bX5xlLjSgWq6kYLQUeG1g5dM= | ||||||
| github.com/hashicorp/hcp-scada-provider v0.2.1 h1:yr+Uxini7SWTZ2t49d3Xi+6+X/rbsSFx8gq6WVcC91c= | github.com/hashicorp/hcp-scada-provider v0.2.1 h1:yr+Uxini7SWTZ2t49d3Xi+6+X/rbsSFx8gq6WVcC91c= | ||||||
| @@ -2488,6 +2494,7 @@ github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvW | |||||||
| github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= | github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= | ||||||
| github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= | github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= | ||||||
| github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= | ||||||
|  | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= | ||||||
| github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= | ||||||
| github.com/sethvargo/go-limiter v0.7.1 h1:wWNhTj0pxjyJ7wuJHpRJpYwJn+bUnjYfw2a85eu5w9U= | github.com/sethvargo/go-limiter v0.7.1 h1:wWNhTj0pxjyJ7wuJHpRJpYwJn+bUnjYfw2a85eu5w9U= | ||||||
| github.com/sethvargo/go-limiter v0.7.1/go.mod h1:C0kbSFbiriE5k2FFOe18M1YZbAR2Fiwf72uGu0CXCcU= | github.com/sethvargo/go-limiter v0.7.1/go.mod h1:C0kbSFbiriE5k2FFOe18M1YZbAR2Fiwf72uGu0CXCcU= | ||||||
| @@ -2680,6 +2687,8 @@ github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ | |||||||
| github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= | github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= | ||||||
| github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= | github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= | ||||||
| github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= | github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= | ||||||
|  | github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= | ||||||
|  | github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= | ||||||
| github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= | github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= | ||||||
| github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= | github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= | ||||||
| github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user