mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-01-29 18:04:15 +02:00
Merge pull request #217 from bradrydzewski/master
use custom Docker images in "services" section of .drone.yml
This commit is contained in:
commit
dd981a4f22
@ -174,16 +174,31 @@ func (b *Builder) setup() error {
|
|||||||
// start all services required for the build
|
// start all services required for the build
|
||||||
// that will get linked to the container.
|
// that will get linked to the container.
|
||||||
for _, service := range b.Build.Services {
|
for _, service := range b.Build.Services {
|
||||||
image, ok := services[service]
|
|
||||||
if !ok {
|
// Parse the name of the Docker image
|
||||||
return fmt.Errorf("Error: Invalid or unknown service %s", service)
|
// And then construct a fully qualified image name
|
||||||
|
owner, name, tag := parseImageName(service)
|
||||||
|
cname := fmt.Sprintf("%s/%s:%s", owner, name, tag)
|
||||||
|
|
||||||
|
// Get the image info
|
||||||
|
img, err := b.dockerClient.Images.Inspect(cname)
|
||||||
|
if err != nil {
|
||||||
|
// Get the image if it doesn't exist
|
||||||
|
if err := b.dockerClient.Images.Pull(cname); err != nil {
|
||||||
|
return fmt.Errorf("Error: Unable to pull image %s", cname)
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err = b.dockerClient.Images.Inspect(cname)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error: Invalid or unknown image %s", cname)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// debugging
|
// debugging
|
||||||
log.Infof("starting service container %s", image.Tag)
|
log.Infof("starting service container %s", cname)
|
||||||
|
|
||||||
// Run the contianer
|
// Run the contianer
|
||||||
run, err := b.dockerClient.Containers.RunDaemonPorts(image.Tag, image.Ports...)
|
run, err := b.dockerClient.Containers.RunDaemonPorts(cname, img.Config.ExposedPorts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -201,7 +216,6 @@ func (b *Builder) setup() error {
|
|||||||
|
|
||||||
// Add the running service to the list
|
// Add the running service to the list
|
||||||
b.services = append(b.services, info)
|
b.services = append(b.services, info)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.writeIdentifyFile(dir); err != nil {
|
if err := b.writeIdentifyFile(dir); err != nil {
|
||||||
@ -319,13 +333,12 @@ func (b *Builder) run() error {
|
|||||||
|
|
||||||
// link service containers
|
// link service containers
|
||||||
for i, service := range b.services {
|
for i, service := range b.services {
|
||||||
image, ok := services[b.Build.Services[i]]
|
// convert name of the image to a slug
|
||||||
if !ok {
|
_, name, _ := parseImageName(b.Build.Services[i])
|
||||||
continue // THIS SHOULD NEVER HAPPEN
|
|
||||||
}
|
|
||||||
// link the service container to our
|
// link the service container to our
|
||||||
// build container.
|
// build container.
|
||||||
host.Links = append(host.Links, service.Name[1:]+":"+image.Name)
|
host.Links = append(host.Links, service.Name[1:]+":"+name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// where are temp files going to go?
|
// where are temp files going to go?
|
||||||
|
@ -3,6 +3,7 @@ package build
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -108,20 +109,23 @@ func TestSetupEmptyImage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSetupUnknownService will test our ability to handle an
|
// TestSetupErrorInspectImage will test our ability to handle a
|
||||||
// unknown or unsupported service (i.e. mysql).
|
// failure when inspecting an image (i.e. bradrydzewski/mysql:latest),
|
||||||
func TestSetupUnknownService(t *testing.T) {
|
// which should trigger a `docker pull`.
|
||||||
b := Builder{}
|
func TestSetupErrorInspectImage(t *testing.T) {
|
||||||
b.Repo = &repo.Repo{}
|
t.Skip()
|
||||||
b.Repo.Path = "git://github.com/drone/drone.git"
|
}
|
||||||
b.Build = &script.Build{}
|
|
||||||
b.Build.Image = "go1.2"
|
// TestSetupErrorPullImage will test our ability to handle a
|
||||||
b.Build.Services = append(b.Build.Services, "not-found")
|
// failure when pulling an image (i.e. bradrydzewski/mysql:latest)
|
||||||
|
func TestSetupErrorPullImage(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
var got, want = b.setup(), "Error: Invalid or unknown service not-found"
|
|
||||||
if got == nil || got.Error() != want {
|
|
||||||
t.Errorf("Expected error %s, got %s", want, got)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSetupErrorRunDaemonPorts will test our ability to handle a
|
// TestSetupErrorRunDaemonPorts will test our ability to handle a
|
||||||
@ -130,6 +134,11 @@ func TestSetupErrorRunDaemonPorts(t *testing.T) {
|
|||||||
setup()
|
setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
|
mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := []byte(`{"config": { "ExposedPorts": { "6379/tcp": {}}}}`)
|
||||||
|
w.Write(data)
|
||||||
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
})
|
})
|
||||||
@ -155,6 +164,11 @@ func TestSetupErrorServiceInspect(t *testing.T) {
|
|||||||
setup()
|
setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
|
mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := []byte(`{"config": { "ExposedPorts": { "6379/tcp": {}}}}`)
|
||||||
|
w.Write(data)
|
||||||
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
|
||||||
body := `{ "Id":"e90e34656806", "Warnings":[] }`
|
body := `{ "Id":"e90e34656806", "Warnings":[] }`
|
||||||
w.Write([]byte(body))
|
w.Write([]byte(body))
|
||||||
@ -188,12 +202,11 @@ func TestSetupErrorImagePull(t *testing.T) {
|
|||||||
setup()
|
setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/v1.9/images/create", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/v1.9/images/create?fromImage=bradrydzewski/mysql&tag=5.5", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// validate ?fromImage=bradrydzewski/go&tag=1.2
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -202,10 +215,11 @@ func TestSetupErrorImagePull(t *testing.T) {
|
|||||||
b.Repo.Path = "git://github.com/drone/drone.git"
|
b.Repo.Path = "git://github.com/drone/drone.git"
|
||||||
b.Build = &script.Build{}
|
b.Build = &script.Build{}
|
||||||
b.Build.Image = "go1.2"
|
b.Build.Image = "go1.2"
|
||||||
|
b.Build.Services = append(b.Build.Services, "mysql")
|
||||||
b.dockerClient = client
|
b.dockerClient = client
|
||||||
|
|
||||||
var got, want = b.setup(), docker.ErrBadRequest
|
var got, want = b.setup(), fmt.Errorf("Error: Unable to pull image bradrydzewski/mysql:5.5")
|
||||||
if got == nil || got != want {
|
if got == nil || got.Error() != want.Error() {
|
||||||
t.Errorf("Expected error %s, got %s", want, got)
|
t.Errorf("Expected error %s, got %s", want, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,19 +127,18 @@ func (c *ContainerService) RunDaemon(conf *Config, host *HostConfig) (*Run, erro
|
|||||||
return run, err
|
return run, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ContainerService) RunDaemonPorts(image string, ports ...string) (*Run, error) {
|
func (c *ContainerService) RunDaemonPorts(image string, ports map[Port]struct{}) (*Run, error) {
|
||||||
// setup configuration
|
// setup configuration
|
||||||
config := Config{Image: image}
|
config := Config{Image: image}
|
||||||
config.ExposedPorts = make(map[Port]struct{})
|
config.ExposedPorts = ports
|
||||||
|
|
||||||
// host configuration
|
// host configuration
|
||||||
host := HostConfig{}
|
host := HostConfig{}
|
||||||
host.PortBindings = make(map[Port][]PortBinding)
|
host.PortBindings = make(map[Port][]PortBinding)
|
||||||
|
|
||||||
// loop through and add ports
|
// loop through and add ports
|
||||||
for _, port := range ports {
|
for port, _ := range ports {
|
||||||
config.ExposedPorts[Port(port+"/tcp")] = struct{}{}
|
host.PortBindings[port] = []PortBinding{{HostIp: "127.0.0.1", HostPort: ""}}
|
||||||
host.PortBindings[Port(port+"/tcp")] = []PortBinding{{HostIp: "127.0.0.1", HostPort: ""}}
|
|
||||||
}
|
}
|
||||||
//127.0.0.1::%s
|
//127.0.0.1::%s
|
||||||
//map[3306/tcp:{}] map[3306/tcp:[{127.0.0.1 }]]
|
//map[3306/tcp:{}] map[3306/tcp:[{127.0.0.1 }]]
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// createUID is a helper function that will
|
// createUID is a helper function that will
|
||||||
@ -26,3 +27,57 @@ func createRandom() []byte {
|
|||||||
}
|
}
|
||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// list of service aliases and their full, canonical names
|
||||||
|
var defaultServices = map[string]string{
|
||||||
|
"cassandra": "relateiq/cassandra:latest",
|
||||||
|
"couchdb": "bradrydzewski/couchdb:1.5",
|
||||||
|
"elasticsearch": "bradrydzewski/elasticsearch:0.90",
|
||||||
|
"memcached": "bradrydzewski/memcached",
|
||||||
|
"mongodb": "bradrydzewski/mongodb:2.4",
|
||||||
|
"mysql": "bradrydzewski/mysql:5.5",
|
||||||
|
"neo4j": "bradrydzewski/neo4j:1.9",
|
||||||
|
"postgres": "bradrydzewski/postgres:9.1",
|
||||||
|
"redis": "bradrydzewski/redis:2.8",
|
||||||
|
"rabbitmq": "bradrydzewski/rabbitmq:3.2",
|
||||||
|
"riak": "guillermo/riak:latest",
|
||||||
|
"zookeeper": "jplock/zookeeper:3.4.5",
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseImageName parses a Docker image name, in the format owner/name:tag,
|
||||||
|
// and returns each segment.
|
||||||
|
//
|
||||||
|
// If the owner is blank, it is assumed to be an official drone image,
|
||||||
|
// and will be prefixed with the appropriate owner name.
|
||||||
|
//
|
||||||
|
// If the tag is empty, it is assumed to be the latest version.
|
||||||
|
func parseImageName(image string) (owner, name, tag string) {
|
||||||
|
owner = "bradrydzewski" // this will eventually change to drone
|
||||||
|
name = image
|
||||||
|
tag = "latest"
|
||||||
|
|
||||||
|
// first we check to see if the image name is an alias
|
||||||
|
// for a known service.
|
||||||
|
//
|
||||||
|
// TODO I'm not a huge fan of this code here. Maybe it
|
||||||
|
// should get handled when the yaml is parsed, and
|
||||||
|
// convert the image and service names in the yaml
|
||||||
|
// to fully qualified names?
|
||||||
|
if cname, ok := defaultServices[image]; ok {
|
||||||
|
name = cname
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(name, "/")
|
||||||
|
if len(parts) == 2 {
|
||||||
|
owner = parts[0]
|
||||||
|
name = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = strings.Split(name, ":")
|
||||||
|
if len(parts) == 2 {
|
||||||
|
name = parts[0]
|
||||||
|
tag = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
36
pkg/build/util_test.go
Normal file
36
pkg/build/util_test.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestParseImageName(t *testing.T) {
|
||||||
|
images := []struct {
|
||||||
|
owner string
|
||||||
|
name string
|
||||||
|
tag string
|
||||||
|
cname string
|
||||||
|
}{
|
||||||
|
// full image name with all 3 sections present
|
||||||
|
{"johnsmith", "redis", "2.8", "johnsmith/redis:2.8"},
|
||||||
|
// image name with no tag specified
|
||||||
|
{"johnsmith", "redis", "latest", "johnsmith/redis"},
|
||||||
|
// image name with no owner specified
|
||||||
|
{"bradrydzewski", "redis", "2.8", "redis:2.8"},
|
||||||
|
// image name with ownly name specified
|
||||||
|
{"bradrydzewski", "redis2", "latest", "redis2"},
|
||||||
|
// image name that is a known alias
|
||||||
|
{"relateiq", "cassandra", "latest", "cassandra"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, img := range images {
|
||||||
|
owner, name, tag := parseImageName(img.cname)
|
||||||
|
if owner != img.owner {
|
||||||
|
t.Errorf("Expected image %s with owner %s, got %s", img.cname, img.owner, owner)
|
||||||
|
}
|
||||||
|
if name != img.name {
|
||||||
|
t.Errorf("Expected image %s with name %s, got %s", img.cname, img.name, name)
|
||||||
|
}
|
||||||
|
if tag != img.tag {
|
||||||
|
t.Errorf("Expected image %s with tag %s, got %s", img.cname, img.tag, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user