github.com/emate/nomad@v0.8.2-wo-binpacking/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/uuid" 12 "github.com/hashicorp/nomad/nomad/structs/config" 13 vapi "github.com/hashicorp/vault/api" 14 "github.com/mitchellh/go-testing-interface" 15 ) 16 17 // TestVault is a test helper. It uses a fork/exec model to create a test Vault 18 // server instance in the background and can be initialized with policies, roles 19 // and backends mounted. The test Vault instances can be used to run a unit test 20 // and offers and easy API to tear itself down on test end. The only 21 // prerequisite is that the Vault binary is on the $PATH. 22 23 // TestVault wraps a test Vault server launched in dev mode, suitable for 24 // testing. 25 type TestVault struct { 26 cmd *exec.Cmd 27 t testing.T 28 waitCh chan error 29 30 Addr string 31 HTTPAddr string 32 RootToken string 33 Config *config.VaultConfig 34 Client *vapi.Client 35 } 36 37 // NewTestVault returns a new TestVault instance that has yet to be started 38 func NewTestVault(t testing.T) *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("vault", "server", "-dev", bind, root) 47 cmd.Stdout = os.Stdout 48 cmd.Stderr = os.Stderr 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 // NewTestVaultDelayed returns a test Vault server that has not been started. 118 // Start must be called and it is the callers responsibility to deal with any 119 // port conflicts that may occur and retry accordingly. 120 func NewTestVaultDelayed(t testing.T) *TestVault { 121 port := freeport.GetT(t, 1)[0] 122 token := uuid.Generate() 123 bind := fmt.Sprintf("-dev-listen-address=127.0.0.1:%d", port) 124 http := fmt.Sprintf("http://127.0.0.1:%d", port) 125 root := fmt.Sprintf("-dev-root-token-id=%s", token) 126 127 cmd := exec.Command("vault", "server", "-dev", bind, root) 128 cmd.Stdout = os.Stdout 129 cmd.Stderr = os.Stderr 130 131 // Build the config 132 conf := vapi.DefaultConfig() 133 conf.Address = http 134 135 // Make the client and set the token to the root token 136 client, err := vapi.NewClient(conf) 137 if err != nil { 138 t.Fatalf("failed to build Vault API client: %v", err) 139 } 140 client.SetToken(token) 141 142 enable := true 143 tv := &TestVault{ 144 cmd: cmd, 145 t: t, 146 Addr: bind, 147 HTTPAddr: http, 148 RootToken: token, 149 Client: client, 150 Config: &config.VaultConfig{ 151 Enabled: &enable, 152 Token: token, 153 Addr: http, 154 }, 155 } 156 157 return tv 158 } 159 160 // Start starts the test Vault server and waits for it to respond to its HTTP 161 // API 162 func (tv *TestVault) Start() error { 163 if err := tv.cmd.Start(); err != nil { 164 tv.t.Fatalf("failed to start vault: %v", err) 165 } 166 167 // Start the waiter 168 tv.waitCh = make(chan error, 1) 169 go func() { 170 err := tv.cmd.Wait() 171 tv.waitCh <- err 172 }() 173 174 // Ensure Vault started 175 select { 176 case err := <-tv.waitCh: 177 return err 178 case <-time.After(time.Duration(500*TestMultiplier()) * time.Millisecond): 179 } 180 181 return tv.waitForAPI() 182 } 183 184 // Stop stops the test Vault server 185 func (tv *TestVault) Stop() { 186 if tv.cmd.Process == nil { 187 return 188 } 189 190 if err := tv.cmd.Process.Kill(); err != nil { 191 tv.t.Errorf("err: %s", err) 192 } 193 if tv.waitCh != nil { 194 <-tv.waitCh 195 } 196 } 197 198 // waitForAPI waits for the Vault HTTP endpoint to start 199 // responding. This is an indication that the agent has started. 200 func (tv *TestVault) waitForAPI() error { 201 var waitErr error 202 WaitForResult(func() (bool, error) { 203 inited, err := tv.Client.Sys().InitStatus() 204 if err != nil { 205 return false, err 206 } 207 return inited, nil 208 }, func(err error) { 209 waitErr = err 210 }) 211 return waitErr 212 } 213 214 // VaultVersion returns the Vault version as a string or an error if it couldn't 215 // be determined 216 func VaultVersion() (string, error) { 217 cmd := exec.Command("vault", "version") 218 out, err := cmd.Output() 219 return string(out), err 220 }