package garage import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" ) // AdminClient is a helper type for performing actions against the garage admin // interface. type AdminClient struct { c *http.Client addr string adminToken string } // NewAdminClient initializes and returns an AdminClient which will use the // given address and adminToken for all requests made. func NewAdminClient(addr, adminToken string) *AdminClient { return &AdminClient{ c: &http.Client{ Transport: http.DefaultTransport.(*http.Transport).Clone(), }, addr: addr, adminToken: adminToken, } } // Do performs an HTTP request with the given method (GET, POST) and path, and // using the json marshaling of the given body as the request body (unless body // is nil). It will JSON unmarshal the response into rcv, unless rcv is nil. func (c *AdminClient) Do( ctx context.Context, rcv interface{}, method, path string, body interface{}, ) error { var bodyR io.Reader if body != nil { bodyBuf := new(bytes.Buffer) bodyR = bodyBuf if err := json.NewEncoder(bodyBuf).Encode(body); err != nil { return fmt.Errorf("json marshaling body: %w", err) } } urlStr := fmt.Sprintf("http://%s%s", c.addr, path) req, err := http.NewRequestWithContext(ctx, method, urlStr, bodyR) if err != nil { return fmt.Errorf("initializing request: %w", err) } req.Header.Set("Authorization", "Bearer "+c.adminToken) res, err := c.c.Do(req) if err != nil { return fmt.Errorf("performing http request: %w", err) } defer res.Body.Close() if res.StatusCode != 200 { return fmt.Errorf("unexpected %s response returned", res.Status) } if rcv == nil { if _, err := io.Copy(io.Discard, res.Body); err != nil { return fmt.Errorf("discarding response body: %w", err) } return nil } if err := json.NewDecoder(res.Body).Decode(rcv); err != nil { return fmt.Errorf("decoding json response body: %w", err) } return nil } // Wait will block until the instance connected to can see at least // ReplicationFactor-1 other garage instances. If the context is canceled it // will return the context error. func (c *AdminClient) Wait(ctx context.Context) error { for { var clusterStatus struct { KnownNodes map[string]struct { IsUp bool `json:"is_up"` } `json:"knownNodes"` } err := c.Do(ctx, &clusterStatus, "GET", "/v0/status", nil) if ctxErr := ctx.Err(); ctxErr != nil { return ctxErr } else if err != nil { continue } var numUp int for _, knownNode := range clusterStatus.KnownNodes { if knownNode.IsUp { numUp++ } } if numUp >= ReplicationFactor-1 { return nil } } }