build/buildlet/remote.go

152 строки
3.8 KiB
Go
Исходник Обычный вид История

// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildlet
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"sync"
"time"
"golang.org/x/build"
)
type UserPass struct {
Username string // "user-$USER"
Password string // buildlet key
}
// A CoordinatorClient makes calls to the build coordinator.
type CoordinatorClient struct {
// Auth specifies how to authenticate to the coordinator.
Auth UserPass
// Instance optionally specifies the build coordinator to connect
// to. If zero, the production coordinator is used.
Instance build.CoordinatorInstance
mu sync.Mutex
hc *http.Client
}
func (cc *CoordinatorClient) instance() build.CoordinatorInstance {
if cc.Instance == "" {
return build.ProdCoordinator
}
return cc.Instance
}
func (cc *CoordinatorClient) client() (*http.Client, error) {
cc.mu.Lock()
defer cc.mu.Unlock()
if cc.hc != nil {
return cc.hc, nil
}
cc.hc = &http.Client{
Transport: &http.Transport{
Dial: defaultDialer(),
DialTLS: cc.instance().TLSDialer(),
},
}
return cc.hc, nil
}
// CreateBuildlet creates a new buildlet of the given type on cc.
// It may expire at any time.
// To release it, call Client.Destroy.
func (cc *CoordinatorClient) CreateBuildlet(buildletType string) (*Client, error) {
hc, err := cc.client()
if err != nil {
return nil, err
}
ipPort, _ := cc.instance().TLSHostPort() // must succeed if client did
form := url.Values{
"type": {buildletType},
}
req, _ := http.NewRequest("POST",
"https://"+ipPort+"/buildlet/create",
strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(cc.Auth.Username, cc.Auth.Password)
// TODO: accept a context for deadline/cancelation
res, err := hc.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
slurp, _ := ioutil.ReadAll(res.Body)
return nil, fmt.Errorf("%s: %s", res.Status, slurp)
}
var rb RemoteBuildlet
if err := json.NewDecoder(res.Body).Decode(&rb); err != nil {
return nil, err
}
if rb.Name == "" {
return nil, errors.New("buildlet: failed to create remote buildlet; unexpected missing name in response")
}
c, err := cc.NamedBuildlet(rb.Name)
if err != nil {
return nil, err
}
return c, nil
}
type RemoteBuildlet struct {
Type string // "openbsd-386"
Name string // "buildlet-adg-openbsd-386-2"
Created time.Time
Expires time.Time
}
func (cc *CoordinatorClient) RemoteBuildlets() ([]RemoteBuildlet, error) {
hc, err := cc.client()
if err != nil {
return nil, err
}
ipPort, _ := cc.instance().TLSHostPort() // must succeed if client did
req, _ := http.NewRequest("GET", "https://"+ipPort+"/buildlet/list", nil)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(cc.Auth.Username, cc.Auth.Password)
res, err := hc.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
slurp, _ := ioutil.ReadAll(res.Body)
return nil, fmt.Errorf("%s: %s", res.Status, slurp)
}
var ret []RemoteBuildlet
if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
return nil, err
}
return ret, nil
}
// NamedBuildlet returns a buildlet client for the named remote buildlet.
// Names are not validated. Use Client.Status to check whether the client works.
func (cc *CoordinatorClient) NamedBuildlet(name string) (*Client, error) {
hc, err := cc.client()
if err != nil {
return nil, err
}
ipPort, _ := cc.instance().TLSHostPort() // must succeed if client did
c := &Client{
baseURL: "https://" + ipPort,
remoteBuildlet: name,
httpClient: hc,
authUser: cc.Auth.Username,
password: cc.Auth.Password,
}
c.setCommon()
return c, nil
}