mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-26 11:51:02 +00:00
Merge pull request #599 from bradrydzewski/remote-workers
Updated Docker Client to accept TLS
This commit is contained in:
commit
1cb74a90e3
7 changed files with 179 additions and 102 deletions
|
@ -117,6 +117,8 @@ user=""
|
||||||
pass=""
|
pass=""
|
||||||
|
|
||||||
[worker]
|
[worker]
|
||||||
|
cert=""
|
||||||
|
key=""
|
||||||
nodes=[
|
nodes=[
|
||||||
"unix:///var/run/docker.sock",
|
"unix:///var/run/docker.sock",
|
||||||
"unix:///var/run/docker.sock"
|
"unix:///var/run/docker.sock"
|
||||||
|
|
30
cli/build.go
30
cli/build.go
|
@ -41,6 +41,21 @@ func NewBuildCommand() cli.Command {
|
||||||
Name: "publish",
|
Name: "publish",
|
||||||
Usage: "runs drone build with publishing enabled",
|
Usage: "runs drone build with publishing enabled",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "docker-host",
|
||||||
|
Value: "",
|
||||||
|
Usage: "docker daemon address",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "docker-cert",
|
||||||
|
Value: "",
|
||||||
|
Usage: "docker daemon tls certificate",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "docker-key",
|
||||||
|
Value: "",
|
||||||
|
Usage: "docker daemon tls key",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) {
|
||||||
buildCommandFunc(c)
|
buildCommandFunc(c)
|
||||||
|
@ -56,6 +71,10 @@ func buildCommandFunc(c *cli.Context) {
|
||||||
var publish = c.Bool("publish")
|
var publish = c.Bool("publish")
|
||||||
var path string
|
var path string
|
||||||
|
|
||||||
|
var dockerhost = c.String("docker-host")
|
||||||
|
var dockercert = c.String("docker-cert")
|
||||||
|
var dockerkey = c.String("docker-key")
|
||||||
|
|
||||||
// the path is provided as an optional argument that
|
// the path is provided as an optional argument that
|
||||||
// will otherwise default to $PWD/.drone.yml
|
// will otherwise default to $PWD/.drone.yml
|
||||||
if len(c.Args()) > 0 {
|
if len(c.Args()) > 0 {
|
||||||
|
@ -80,12 +99,17 @@ func buildCommandFunc(c *cli.Context) {
|
||||||
log.SetPriority(log.LOG_DEBUG) //LOG_NOTICE
|
log.SetPriority(log.LOG_DEBUG) //LOG_NOTICE
|
||||||
docker.Logging = false
|
docker.Logging = false
|
||||||
|
|
||||||
var exit, _ = run(path, identity, publish, deploy, privileged)
|
var exit, _ = run(path, identity, dockerhost, dockercert, dockerkey, publish, deploy, privileged)
|
||||||
os.Exit(exit)
|
os.Exit(exit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(path, identity string, publish, deploy, privileged bool) (int, error) {
|
// TODO this has gotten a bit out of hand. refactor input params
|
||||||
dockerClient := docker.New()
|
func run(path, identity, dockerhost, dockercert, dockerkey string, publish, deploy, privileged bool) (int, error) {
|
||||||
|
dockerClient, err := docker.NewHostCertFile(dockerhost, dockercert, dockerkey)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err.Error())
|
||||||
|
return EXIT_STATUS, err
|
||||||
|
}
|
||||||
|
|
||||||
// parse the private environment variables
|
// parse the private environment variables
|
||||||
envs := getParamMap("DRONE_ENV_")
|
envs := getParamMap("DRONE_ENV_")
|
||||||
|
|
|
@ -65,6 +65,8 @@ datasource="/var/lib/drone/drone.sqlite"
|
||||||
# pass=""
|
# pass=""
|
||||||
|
|
||||||
# [worker]
|
# [worker]
|
||||||
|
# cert=""
|
||||||
|
# key=""
|
||||||
# nodes=[
|
# nodes=[
|
||||||
# "unix:///var/run/docker.sock",
|
# "unix:///var/run/docker.sock",
|
||||||
# "unix:///var/run/docker.sock"
|
# "unix:///var/run/docker.sock"
|
||||||
|
|
|
@ -60,9 +60,8 @@ var (
|
||||||
pub *pubsub.PubSub
|
pub *pubsub.PubSub
|
||||||
|
|
||||||
// Docker configuration details.
|
// Docker configuration details.
|
||||||
tlscacert = config.String("docker-tlscacert", "")
|
dockercrt = config.String("docker-cert", "")
|
||||||
tlscert = config.String("docker-tlscert", "")
|
dockerkey = config.String("docker-key", "")
|
||||||
tlskey = config.String("docker-tlskey", "")
|
|
||||||
nodes StringArr
|
nodes StringArr
|
||||||
|
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
|
|
|
@ -262,7 +262,7 @@ func (b *Builder) setup() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if we have problems with the image make sure
|
// if we have problems with the image make sure
|
||||||
// we remove it before we exit
|
// we remove it before we exit
|
||||||
b.dockerClient.Images.Remove(id)
|
log.Errf("failed to verify build image %s", id)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -12,6 +13,7 @@ import (
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
|
@ -32,35 +34,125 @@ var Logging = true
|
||||||
|
|
||||||
// New creates an instance of the Docker Client
|
// New creates an instance of the Docker Client
|
||||||
func New() *Client {
|
func New() *Client {
|
||||||
c := &Client{}
|
return NewHost("")
|
||||||
|
|
||||||
c.setHost(DEFAULTUNIXSOCKET)
|
|
||||||
|
|
||||||
c.Images = &ImageService{c}
|
|
||||||
c.Containers = &ContainerService{c}
|
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHost(address string) *Client {
|
func NewHost(uri string) *Client {
|
||||||
c := &Client{}
|
var cli, _ = NewHostCert(uri, nil, nil)
|
||||||
|
return cli
|
||||||
|
}
|
||||||
|
|
||||||
// parse the address and split
|
func NewHostCertFile(uri, cert, key string) (*Client, error) {
|
||||||
pieces := strings.Split(address, "://")
|
if len(key) == 0 || len(cert) == 0 {
|
||||||
if len(pieces) == 2 {
|
return NewHostCert(uri, nil, nil)
|
||||||
c.proto = pieces[0]
|
}
|
||||||
c.addr = pieces[1]
|
certfile, err := ioutil.ReadFile(cert)
|
||||||
} else if len(pieces) == 1 {
|
if err != nil {
|
||||||
c.addr = pieces[0]
|
return nil, err
|
||||||
|
}
|
||||||
|
keyfile, err := ioutil.ReadFile(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewHostCert(uri, certfile, keyfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHostCert(uri string, cert, key []byte) (*Client, error) {
|
||||||
|
var host = GetHost(uri)
|
||||||
|
var proto, addr = SplitProtoAddr(host)
|
||||||
|
|
||||||
|
var cli = new(Client)
|
||||||
|
cli.proto = proto
|
||||||
|
cli.addr = addr
|
||||||
|
cli.scheme = "http"
|
||||||
|
cli.Images = &ImageService{cli}
|
||||||
|
cli.Containers = &ContainerService{cli}
|
||||||
|
|
||||||
|
// if no certificate is provided returns the
|
||||||
|
// client with no TLS configured.
|
||||||
|
if cert == nil || key == nil || len(cert) == 0 || len(key) == 0 {
|
||||||
|
return cli, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Images = &ImageService{c}
|
// loads the key value pair in pem format
|
||||||
c.Containers = &ContainerService{c}
|
pem, err := tls.X509KeyPair(cert, key)
|
||||||
return c
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup the client TLS and store the certificate.
|
||||||
|
// also skip verification since we are (typically)
|
||||||
|
// going to be using certs for IP addresses.
|
||||||
|
cli.scheme = "https"
|
||||||
|
cli.tls = new(tls.Config)
|
||||||
|
cli.tls.InsecureSkipVerify = true
|
||||||
|
cli.tls.Certificates = []tls.Certificate{pem}
|
||||||
|
|
||||||
|
// disable compression for local socket communication.
|
||||||
|
if cli.proto == DEFAULTPROTOCOL {
|
||||||
|
cli.trans.DisableCompression = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a transport that uses the custom tls configuration
|
||||||
|
// to securely connect to remote Docker clients.
|
||||||
|
cli.trans = &http.Transport{
|
||||||
|
TLSClientConfig: cli.tls,
|
||||||
|
Dial: func(dial_network, dial_addr string) (net.Conn, error) {
|
||||||
|
return net.DialTimeout(cli.proto, cli.addr, 32*time.Second)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cli, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHost returns the Docker Host address in order to
|
||||||
|
// connect to the Docker Daemon. It implements a very
|
||||||
|
// simple set of fallthrough logic to determine which
|
||||||
|
// address to use.
|
||||||
|
func GetHost(host string) string {
|
||||||
|
// if a default value was provided this
|
||||||
|
// shoudl be used
|
||||||
|
if len(host) != 0 {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
// else attempt to use the DOCKER_HOST
|
||||||
|
// environment variable
|
||||||
|
var env = os.Getenv("DOCKER_HOST")
|
||||||
|
if len(env) != 0 {
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
// else check to see if the default unix
|
||||||
|
// socket exists and return
|
||||||
|
_, err := os.Stat(DEFAULTUNIXSOCKET)
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Sprintf("%s://%s", DEFAULTPROTOCOL, DEFAULTUNIXSOCKET)
|
||||||
|
}
|
||||||
|
// else return the standard TCP address
|
||||||
|
return fmt.Sprintf("tcp://0.0.0.0:%d", DEFAULTHTTPPORT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitProtoAddr is a helper function that splits
|
||||||
|
// a host into Protocol and Address.
|
||||||
|
func SplitProtoAddr(host string) (string, string) {
|
||||||
|
var parts = strings.Split(host, "://")
|
||||||
|
var proto, addr string
|
||||||
|
switch {
|
||||||
|
case len(parts) == 2:
|
||||||
|
proto = parts[0]
|
||||||
|
addr = parts[1]
|
||||||
|
default:
|
||||||
|
proto = "tcp"
|
||||||
|
addr = parts[0]
|
||||||
|
}
|
||||||
|
return proto, addr
|
||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
proto string
|
tls *tls.Config
|
||||||
addr string
|
trans *http.Transport
|
||||||
|
scheme string
|
||||||
|
proto string
|
||||||
|
addr string
|
||||||
|
|
||||||
Images *ImageService
|
Images *ImageService
|
||||||
Containers *ContainerService
|
Containers *ContainerService
|
||||||
|
@ -87,28 +179,6 @@ var (
|
||||||
ErrBadRequest = errors.New("Bad Request")
|
ErrBadRequest = errors.New("Bad Request")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) setHost(defaultUnixSocket string) {
|
|
||||||
c.proto = DEFAULTPROTOCOL
|
|
||||||
c.addr = defaultUnixSocket
|
|
||||||
|
|
||||||
if os.Getenv("DOCKER_HOST") != "" {
|
|
||||||
pieces := strings.Split(os.Getenv("DOCKER_HOST"), "://")
|
|
||||||
if len(pieces) == 2 {
|
|
||||||
c.proto = pieces[0]
|
|
||||||
c.addr = pieces[1]
|
|
||||||
} else if len(pieces) == 1 {
|
|
||||||
c.addr = pieces[0]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if the default socket doesn't exist then
|
|
||||||
// we'll try to connect to the default tcp address
|
|
||||||
if _, err := os.Stat(defaultUnixSocket); err != nil {
|
|
||||||
c.proto = "tcp"
|
|
||||||
c.addr = "0.0.0.0:2375"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function used to make HTTP requests to the Docker daemon.
|
// helper function used to make HTTP requests to the Docker daemon.
|
||||||
func (c *Client) do(method, path string, in, out interface{}) error {
|
func (c *Client) do(method, path string, in, out interface{}) error {
|
||||||
// if data input is provided, serialize to JSON
|
// if data input is provided, serialize to JSON
|
||||||
|
@ -133,16 +203,13 @@ func (c *Client) do(method, path string, in, out interface{}) error {
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
// dial the host server
|
// dial the host server
|
||||||
req.Host = c.addr
|
req.URL.Host = c.addr
|
||||||
dial, err := net.Dial(c.proto, c.addr)
|
req.URL.Scheme = "http"
|
||||||
if err != nil {
|
if c.tls != nil {
|
||||||
return err
|
req.URL.Scheme = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
// make the request
|
resp, err := c.HTTPClient().Do(req)
|
||||||
conn := httputil.NewClientConn(dial, nil)
|
|
||||||
resp, err := conn.Do(req)
|
|
||||||
defer conn.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -184,7 +251,7 @@ func (c *Client) hijack(method, path string, setRawTerminal bool, out io.Writer)
|
||||||
req.Header.Set("Content-Type", "plain/text")
|
req.Header.Set("Content-Type", "plain/text")
|
||||||
req.Host = c.addr
|
req.Host = c.addr
|
||||||
|
|
||||||
dial, err := net.Dial(c.proto, c.addr)
|
dial, err := c.Dial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
if strings.Contains(err.Error(), "connection refused") {
|
||||||
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
|
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
|
||||||
|
@ -239,16 +306,13 @@ func (c *Client) stream(method, path string, in io.Reader, out io.Writer, header
|
||||||
req.Header.Set("Content-Type", "plain/text")
|
req.Header.Set("Content-Type", "plain/text")
|
||||||
|
|
||||||
// dial the host server
|
// dial the host server
|
||||||
req.Host = c.addr
|
req.URL.Host = c.addr
|
||||||
dial, err := net.Dial(c.proto, c.addr)
|
req.URL.Scheme = "http"
|
||||||
if err != nil {
|
if c.tls != nil {
|
||||||
return err
|
req.URL.Scheme = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
// make the request
|
resp, err := c.HTTPClient().Do(req)
|
||||||
conn := httputil.NewClientConn(dial, nil)
|
|
||||||
resp, err := conn.Do(req)
|
|
||||||
defer conn.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -270,6 +334,7 @@ func (c *Client) stream(method, path string, in io.Reader, out io.Writer, header
|
||||||
|
|
||||||
// If no output we exit now with no errors
|
// If no output we exit now with no errors
|
||||||
if out == nil {
|
if out == nil {
|
||||||
|
io.Copy(ioutil.Discard, resp.Body)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,3 +354,23 @@ func (c *Client) stream(method, path string, in io.Reader, out io.Writer, header
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) HTTPClient() *http.Client {
|
||||||
|
if c.trans != nil {
|
||||||
|
return &http.Client{Transport: c.trans}
|
||||||
|
}
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Dial: func(dial_network, dial_addr string) (net.Conn, error) {
|
||||||
|
return net.DialTimeout(c.proto, c.addr, 32*time.Second)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Dial() (net.Conn, error) {
|
||||||
|
if c.tls != nil && c.proto != "unix" {
|
||||||
|
return tls.Dial(c.proto, c.addr, c.tls)
|
||||||
|
}
|
||||||
|
return net.Dial(c.proto, c.addr)
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -31,37 +30,3 @@ func TestInvalidHostFromEnv(t *testing.T) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSocketHost(t *testing.T) {
|
|
||||||
// create temporary file to represent the docker socket
|
|
||||||
file, err := ioutil.TempFile("", "TestDefaultUnixHost")
|
|
||||||
if err != nil {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
file.Close()
|
|
||||||
defer os.Remove(file.Name())
|
|
||||||
|
|
||||||
client := &Client{}
|
|
||||||
client.setHost(file.Name())
|
|
||||||
|
|
||||||
if client.proto != "unix" {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
if client.addr != file.Name() {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultTcpHost(t *testing.T) {
|
|
||||||
client := &Client{}
|
|
||||||
client.setHost("/tmp/missing_socket")
|
|
||||||
|
|
||||||
if client.proto != "tcp" {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
if client.addr != "0.0.0.0:2375" {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue