blob: cd80ca757aaf045e9668eab415021ea99851c03d [file]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package devserver
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"os"
)
const gsDownloadURL = "https://storage.googleapis.com"
// PseudoClient is an implementation of Client to simulate devservers without credentials.
type PseudoClient struct {
cl *http.Client
baseURL string
}
var _ Client = &PseudoClient{}
type pseudoClientOptions struct {
cl *http.Client
baseURL string
}
// PseudoClientOption is an option accepted by NewPseudoClient to configure
// PseudoClient initialization.
type PseudoClientOption func(o *pseudoClientOptions)
// WithHTTPClient returns an option that specifies http.Client used by
// PseudoClient.
func WithHTTPClient(cl *http.Client) PseudoClientOption {
return func(o *pseudoClientOptions) { o.cl = cl }
}
// WithBaseURL returns an option that specifies the base URL of Google Cloud
// Storage HTTP API.
func WithBaseURL(baseURL string) PseudoClientOption {
return func(o *pseudoClientOptions) { o.baseURL = baseURL }
}
// NewPseudoClient creates a PseudoClient.
func NewPseudoClient(opts ...PseudoClientOption) *PseudoClient {
o := &pseudoClientOptions{
cl: defaultHTTPClient,
baseURL: gsDownloadURL,
}
for _, opts := range opts {
opts(o)
}
return &PseudoClient{cl: o.cl, baseURL: o.baseURL}
}
// TearDown does nothing.
func (c *PseudoClient) TearDown() error {
return nil
}
// Stage returns a url to GCS directly from storage.googleapis.com.
func (c *PseudoClient) Stage(ctx context.Context, gsURL string) (*url.URL, error) {
bucket, path, err := ParseGSURL(gsURL)
if err != nil {
return nil, err
}
dlURL, _ := url.Parse(c.baseURL)
dlURL.Path = fmt.Sprintf("/%s/%s", bucket, path)
return dlURL, nil
}
// Open downloads a file on GCS directly from storage.googleapis.com.
func (c *PseudoClient) Open(ctx context.Context, gsURL string) (io.ReadCloser, error) {
dlURL, err := c.Stage(ctx, gsURL)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", dlURL.String(), nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
res, err := c.cl.Do(req)
if err != nil {
return nil, err
}
switch res.StatusCode {
case http.StatusOK:
return res.Body, nil
case http.StatusNotFound:
res.Body.Close()
return nil, os.ErrNotExist
default:
res.Body.Close()
return nil, fmt.Errorf("got status %d", res.StatusCode)
}
}