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