"net/http"
"net/http/httputil"
"net/url"
+ "os"
"strings"
"git.arvados.org/arvados.git/lib/controller/rpc"
func (conn *Conn) ContainerLog(ctx context.Context, opts arvados.ContainerLogOptions) (http.Handler, error) {
ctr, err := conn.railsProxy.ContainerGet(ctx, arvados.GetOptions{UUID: opts.UUID, Select: []string{"uuid", "state", "gateway_address", "log"}})
if err != nil {
+ if se := httpserver.HTTPStatusError(nil); errors.As(err, &se) && se.HTTPStatus() == http.StatusUnauthorized {
+ // Hint to WebDAV client that we accept HTTP basic auth.
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Www-Authenticate", "Basic realm=\"collections\"")
+ w.WriteHeader(http.StatusUnauthorized)
+ }), nil
+ }
return nil, err
}
if ctr.GatewayAddress == "" ||
myHostname := u.Hostname()
var webdavBase arvados.URL
var ok bool
- for webdavBase = range conn.cluster.Services.WebDAVDownload.InternalURLs {
+ for webdavBase = range conn.cluster.Services.WebDAV.InternalURLs {
ok = true
u := url.URL(webdavBase)
if h := u.Hostname(); h == "127.0.0.1" || h == "0.0.0.0" || h == "::1" || h == myHostname {
}
proxy := &httputil.ReverseProxy{
Director: func(r *http.Request) {
- r.URL = &url.URL{
- Scheme: webdavBase.Scheme,
- Host: webdavBase.Host,
- Path: "/by_id/" + url.PathEscape(ctr.Log) + opts.Path,
- }
- // Our outgoing Host header must match
- // WebDAVDownload.ExternalURL, otherwise
- // keep-web does not accept an auth token.
- r.Host = conn.cluster.Services.WebDAVDownload.ExternalURL.Host
+ r.URL.Scheme = webdavBase.Scheme
+ r.URL.Host = webdavBase.Host
+ // Outgoing Host header specifies the
+ // collection ID.
+ r.Host = strings.Replace(ctr.Log, "+", "-", -1) + ".internal"
// We already checked permission on the
// container, so we can use a root token here
// instead of counting on the "access to log
// permission check, which can be racy when a
// request gets retried with a new container.
r.Header.Set("Authorization", "Bearer "+conn.cluster.SystemRootToken)
+ // We can't change r.URL.Path without
+ // confusing WebDAV (request body and response
+ // headers refer to the same paths) so we tell
+ // keep-web to map the log collection onto the
+ // containers/X/log/ namespace.
+ r.Header.Set("X-Webdav-Prefix", "/arvados/v1/containers/"+ctr.UUID+"/log")
},
}
if conn.cluster.TLS.Insecure {
FileSystem: webdav.NewMemFS(),
LockSystem: webdavfs.NoLockSystem,
Logger: func(r *http.Request, err error) {
- if err != nil {
+ if err != nil && !os.IsNotExist(err) {
ctxlog.FromContext(r.Context()).WithError(err).Info("webdav error on empty collection fs")
}
},
return func() (net.Conn, string, string, error) {
rawconn, err := (&net.Dialer{}).DialContext(ctx, "tcp", ctr.GatewayAddress)
if err != nil {
- err = httpserver.ErrorWithStatus(err, http.StatusServiceUnavailable)
+ return nil, "", "", httpserver.ErrorWithStatus(err, http.StatusServiceUnavailable)
}
return conn.dialGatewayTLS(ctx, ctr, rawconn)
}, nil, nil
return func() (net.Conn, string, string, error) {
rawconn, err := tunnel.Open()
if err != nil {
- err = httpserver.ErrorWithStatus(err, http.StatusServiceUnavailable)
+ return nil, "", "", httpserver.ErrorWithStatus(err, http.StatusServiceUnavailable)
}
return conn.dialGatewayTLS(ctx, ctr, rawconn)
}, nil, nil