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 }