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 }