2015-07-06 02:22:16 +03:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2015-10-24 06:37:37 +03:00
|
|
|
func (cc *CoordinatorClient) RemoteBuildlets() ([]RemoteBuildlet, error) {
|
2015-07-06 02:22:16 +03:00
|
|
|
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
|
|
|
|
}
|