github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/testutil/vault.go (about)

     1  package testutil
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"os"
     7  	"os/exec"
     8  	"time"
     9  
    10  	"github.com/hashicorp/nomad/nomad/structs"
    11  	"github.com/hashicorp/nomad/nomad/structs/config"
    12  	vapi "github.com/hashicorp/vault/api"
    13  	"github.com/mitchellh/go-testing-interface"
    14  )
    15  
    16  // TestVault is a test helper. It uses a fork/exec model to create a test Vault
    17  // server instance in the background and can be initialized with policies, roles
    18  // and backends mounted. The test Vault instances can be used to run a unit test
    19  // and offers and easy API to tear itself down on test end. The only
    20  // prerequisite is that the Vault binary is on the $PATH.
    21  
    22  // TestVault wraps a test Vault server launched in dev mode, suitable for
    23  // testing.
    24  type TestVault struct {
    25  	cmd    *exec.Cmd
    26  	t      testing.T
    27  	waitCh chan error
    28  
    29  	Addr      string
    30  	HTTPAddr  string
    31  	RootToken string
    32  	Config    *config.VaultConfig
    33  	Client    *vapi.Client
    34  }
    35  
    36  // NewTestVault returns a new TestVault instance that has yet to be started
    37  func NewTestVault(t testing.T) *TestVault {
    38  	for i := 10; i >= 0; i-- {
    39  		port := getPort()
    40  		token := structs.GenerateUUID()
    41  		bind := fmt.Sprintf("-dev-listen-address=127.0.0.1:%d", port)
    42  		http := fmt.Sprintf("http://127.0.0.1:%d", port)
    43  		root := fmt.Sprintf("-dev-root-token-id=%s", token)
    44  
    45  		cmd := exec.Command("vault", "server", "-dev", bind, root)
    46  		cmd.Stdout = os.Stdout
    47  		cmd.Stderr = os.Stderr
    48  
    49  		// Build the config
    50  		conf := vapi.DefaultConfig()
    51  		conf.Address = http
    52  
    53  		// Make the client and set the token to the root token
    54  		client, err := vapi.NewClient(conf)
    55  		if err != nil {
    56  			t.Fatalf("failed to build Vault API client: %v", err)
    57  		}
    58  		client.SetToken(token)
    59  
    60  		enable := true
    61  		tv := &TestVault{
    62  			cmd:       cmd,
    63  			t:         t,
    64  			Addr:      bind,
    65  			HTTPAddr:  http,
    66  			RootToken: token,
    67  			Client:    client,
    68  			Config: &config.VaultConfig{
    69  				Enabled: &enable,
    70  				Token:   token,
    71  				Addr:    http,
    72  			},
    73  		}
    74  
    75  		if err := tv.cmd.Start(); err != nil {
    76  			tv.t.Fatalf("failed to start vault: %v", err)
    77  		}
    78  
    79  		// Start the waiter
    80  		tv.waitCh = make(chan error, 1)
    81  		go func() {
    82  			err := tv.cmd.Wait()
    83  			tv.waitCh <- err
    84  		}()
    85  
    86  		// Ensure Vault started
    87  		var startErr error
    88  		select {
    89  		case startErr = <-tv.waitCh:
    90  		case <-time.After(time.Duration(500*TestMultiplier()) * time.Millisecond):
    91  		}
    92  
    93  		if startErr != nil && i == 0 {
    94  			t.Fatalf("failed to start vault: %v", startErr)
    95  		} else if startErr != nil {
    96  			wait := time.Duration(rand.Int31n(2000)) * time.Millisecond
    97  			time.Sleep(wait)
    98  			continue
    99  		}
   100  
   101  		waitErr := tv.waitForAPI()
   102  		if waitErr != nil && i == 0 {
   103  			t.Fatalf("failed to start vault: %v", waitErr)
   104  		} else if waitErr != nil {
   105  			wait := time.Duration(rand.Int31n(2000)) * time.Millisecond
   106  			time.Sleep(wait)
   107  			continue
   108  		}
   109  
   110  		return tv
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  // NewTestVaultDelayed returns a test Vault server that has not been started.
   117  // Start must be called and it is the callers responsibility to deal with any
   118  // port conflicts that may occur and retry accordingly.
   119  func NewTestVaultDelayed(t testing.T) *TestVault {
   120  	port := getPort()
   121  	token := structs.GenerateUUID()
   122  	bind := fmt.Sprintf("-dev-listen-address=127.0.0.1:%d", port)
   123  	http := fmt.Sprintf("http://127.0.0.1:%d", port)
   124  	root := fmt.Sprintf("-dev-root-token-id=%s", token)
   125  
   126  	cmd := exec.Command("vault", "server", "-dev", bind, root)
   127  	cmd.Stdout = os.Stdout
   128  	cmd.Stderr = os.Stderr
   129  
   130  	// Build the config
   131  	conf := vapi.DefaultConfig()
   132  	conf.Address = http
   133  
   134  	// Make the client and set the token to the root token
   135  	client, err := vapi.NewClient(conf)
   136  	if err != nil {
   137  		t.Fatalf("failed to build Vault API client: %v", err)
   138  	}
   139  	client.SetToken(token)
   140  
   141  	enable := true
   142  	tv := &TestVault{
   143  		cmd:       cmd,
   144  		t:         t,
   145  		Addr:      bind,
   146  		HTTPAddr:  http,
   147  		RootToken: token,
   148  		Client:    client,
   149  		Config: &config.VaultConfig{
   150  			Enabled: &enable,
   151  			Token:   token,
   152  			Addr:    http,
   153  		},
   154  	}
   155  
   156  	return tv
   157  }
   158  
   159  // Start starts the test Vault server and waits for it to respond to its HTTP
   160  // API
   161  func (tv *TestVault) Start() error {
   162  	if err := tv.cmd.Start(); err != nil {
   163  		tv.t.Fatalf("failed to start vault: %v", err)
   164  	}
   165  
   166  	// Start the waiter
   167  	tv.waitCh = make(chan error, 1)
   168  	go func() {
   169  		err := tv.cmd.Wait()
   170  		tv.waitCh <- err
   171  	}()
   172  
   173  	// Ensure Vault started
   174  	select {
   175  	case err := <-tv.waitCh:
   176  		return err
   177  	case <-time.After(time.Duration(500*TestMultiplier()) * time.Millisecond):
   178  	}
   179  
   180  	return tv.waitForAPI()
   181  }
   182  
   183  // Stop stops the test Vault server
   184  func (tv *TestVault) Stop() {
   185  	if tv.cmd.Process == nil {
   186  		return
   187  	}
   188  
   189  	if err := tv.cmd.Process.Kill(); err != nil {
   190  		tv.t.Errorf("err: %s", err)
   191  	}
   192  	if tv.waitCh != nil {
   193  		<-tv.waitCh
   194  	}
   195  }
   196  
   197  // waitForAPI waits for the Vault HTTP endpoint to start
   198  // responding. This is an indication that the agent has started.
   199  func (tv *TestVault) waitForAPI() error {
   200  	var waitErr error
   201  	WaitForResult(func() (bool, error) {
   202  		inited, err := tv.Client.Sys().InitStatus()
   203  		if err != nil {
   204  			return false, err
   205  		}
   206  		return inited, nil
   207  	}, func(err error) {
   208  		waitErr = err
   209  	})
   210  	return waitErr
   211  }
   212  
   213  func getPort() int {
   214  	return 1030 + int(rand.Int31n(6440))
   215  }
   216  
   217  // VaultVersion returns the Vault version as a string or an error if it couldn't
   218  // be determined
   219  func VaultVersion() (string, error) {
   220  	cmd := exec.Command("vault", "version")
   221  	out, err := cmd.Output()
   222  	return string(out), err
   223  }