Skip to content
Snippets Groups Projects
Commit f356cf86 authored by Kamil Trzcinski's avatar Kamil Trzcinski
Browse files

Added support for receiving and defining allowed images and services from the Coordinator

parent d790f643
Branches
Tags
No related merge requests found
...@@ -28,6 +28,8 @@ type DockerConfig struct { ...@@ -28,6 +28,8 @@ type DockerConfig struct {
Links []string `toml:"links" json:"links"` Links []string `toml:"links" json:"links"`
Services []string `toml:"services" json:"services"` Services []string `toml:"services" json:"services"`
WaitForServicesTimeout *int `toml:"wait_for_services_timeout" json:"wait_for_services_timeout"` WaitForServicesTimeout *int `toml:"wait_for_services_timeout" json:"wait_for_services_timeout"`
AllowedImages []string `toml:"allowed_images" json:"allowed_images"`
AllowedServices []string `toml:"allowed_services" json:"allowed_services"`
} }
type ParallelsConfig struct { type ParallelsConfig struct {
......
...@@ -88,6 +88,8 @@ This defines the Docker Container parameters. ...@@ -88,6 +88,8 @@ This defines the Docker Container parameters.
| `extra_hosts` | specify hosts that should be defined in container environment | | `extra_hosts` | specify hosts that should be defined in container environment |
| `links` | specify containers which should be linked with building container | | `links` | specify containers which should be linked with building container |
| `services` | specify additional services that should be run with build. Please visit [Docker Registry](https://registry.hub.docker.com/) for list of available applications. Each service will be run in separate container and linked to the build. | | `services` | specify additional services that should be run with build. Please visit [Docker Registry](https://registry.hub.docker.com/) for list of available applications. Each service will be run in separate container and linked to the build. |
| `allowed_images` | specify wildcard list of images that can be specified in .gitlab-ci.yml |
| `allowed_services` | specify wildcard list of services that can be specified in .gitlab-ci.yml |
Example: Example:
...@@ -106,6 +108,8 @@ Example: ...@@ -106,6 +108,8 @@ Example:
extra_hosts = ["other-host:127.0.0.1"] extra_hosts = ["other-host:127.0.0.1"]
links = ["mysql_container:mysql"] links = ["mysql_container:mysql"]
services = ["mysql", "redis:2.8", "postgres:9"] services = ["mysql", "redis:2.8", "postgres:9"]
allowed_images = ["ruby:*", "python:*", "php:*"]
allowed_services = ["postgres:9.4", "postgres:latest"]
``` ```
#### Volumes in the [runners.docker] section #### Volumes in the [runners.docker] section
......
...@@ -26,7 +26,7 @@ type DockerExecutor struct { ...@@ -26,7 +26,7 @@ type DockerExecutor struct {
services []*docker.Container services []*docker.Container
} }
func (s *DockerExecutor) getImage(imageName string) (*docker.Image, error) { func (s *DockerExecutor) getDockerImage(imageName string) (*docker.Image, error) {
s.Debugln("Looking for image", imageName, "...") s.Debugln("Looking for image", imageName, "...")
image, err := s.client.InspectImage(imageName) image, err := s.client.InspectImage(imageName)
if err == nil { if err == nil {
...@@ -98,7 +98,7 @@ func (s *DockerExecutor) addCacheVolume(binds, volumesFrom *[]string, containerP ...@@ -98,7 +98,7 @@ func (s *DockerExecutor) addCacheVolume(binds, volumesFrom *[]string, containerP
// create new cache container for that project // create new cache container for that project
if container == nil { if container == nil {
// get busybox image // get busybox image
cacheImage, err := s.getImage("busybox:latest") cacheImage, err := s.getDockerImage("busybox:latest")
if err != nil { if err != nil {
return err return err
} }
...@@ -171,26 +171,32 @@ func (s *DockerExecutor) createVolumes(image *docker.Image, projectPath string) ...@@ -171,26 +171,32 @@ func (s *DockerExecutor) createVolumes(image *docker.Image, projectPath string)
return binds, volumesFrom, nil return binds, volumesFrom, nil
} }
func (s *DockerExecutor) splitServiceAndVersion(service string) (string, string) { func (s *DockerExecutor) splitServiceAndVersion(serviceDescription string) (string, string, string) {
splits := strings.SplitN(service, ":", 2) splits := strings.SplitN(serviceDescription, ":", 2)
service := ""
version := "latest"
switch len(splits) { switch len(splits) {
case 1: case 1:
return splits[0], "latest" service = splits[0]
case 2: case 2:
return splits[0], splits[1] service = splits[0]
version = splits[1]
default: default:
return "", "" return "", "", ""
} }
linkName := strings.Replace(service, "/", "__", -1)
return service, version, linkName
} }
func (s *DockerExecutor) createService(service, version string) (*docker.Container, error) { func (s *DockerExecutor) createService(service, version string) (*docker.Container, error) {
if len(service) == 0 { if len(service) == 0 {
return nil, errors.New("Invalid service name") return nil, errors.New("invalid service name")
} }
serviceImage, err := s.getImage(service + ":" + version) serviceImage, err := s.getDockerImage(service + ":" + version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -228,18 +234,51 @@ func (s *DockerExecutor) createService(service, version string) (*docker.Contain ...@@ -228,18 +234,51 @@ func (s *DockerExecutor) createService(service, version string) (*docker.Contain
return container, nil return container, nil
} }
func (s *DockerExecutor) getServiceNames() ([]string, error) {
services := s.Config.Docker.Services
if servicesOption, ok := s.Build.Options["services"].([]interface{}); ok {
for _, service := range servicesOption {
serviceName, ok := service.(string)
if !ok {
s.Errorln("Invalid service name passed:", service)
return nil, errors.New("invalid service name")
}
err := s.verifyAllowedImage(serviceName, "services", s.Config.Docker.AllowedServices...)
if err != nil {
return nil, err
}
services = append(services, serviceName)
}
}
return services, nil
}
func (s *DockerExecutor) createServices() ([]string, error) { func (s *DockerExecutor) createServices() ([]string, error) {
var links []string serviceNames, err := s.getServiceNames()
if err != nil {
return nil, err
}
linksMap := make(map[string]*docker.Container)
for _, serviceDescription := range serviceNames {
service, version, linkName := s.splitServiceAndVersion(serviceDescription)
if linksMap[linkName] != nil {
s.Warningln("Service", serviceDescription, "is already created. Ignoring.")
continue
}
for _, serviceDescription := range s.Config.Docker.Services {
service, version := s.splitServiceAndVersion(serviceDescription)
container, err := s.createService(service, version) container, err := s.createService(service, version)
if err != nil { if err != nil {
return links, err return nil, err
} }
s.Debugln("Created service", service, version, "as", container.ID) s.Debugln("Created service", serviceDescription, "as", container.ID)
links = append(links, container.Name+":"+strings.Replace(service, "/", "__", -1)) linksMap[linkName] = container
s.services = append(s.services, container) s.services = append(s.services, container)
} }
...@@ -262,6 +301,11 @@ func (s *DockerExecutor) createServices() ([]string, error) { ...@@ -262,6 +301,11 @@ func (s *DockerExecutor) createServices() ([]string, error) {
wg.Wait() wg.Wait()
} }
var links []string
for linkName, container := range linksMap {
links = append(links, container.ID + ":" + linkName)
}
return links, nil return links, nil
} }
...@@ -380,6 +424,41 @@ func (s *DockerExecutor) removeContainer(id string) error { ...@@ -380,6 +424,41 @@ func (s *DockerExecutor) removeContainer(id string) error {
return err return err
} }
func (s *DockerExecutor) verifyAllowedImage(image, optionName string, allowedImages... string) error {
for _, allowedImage := range allowedImages {
ok, _ := filepath.Match(allowedImage, image)
if ok {
return nil
}
}
s.Println()
if len(allowedImages) != 0 {
s.Errorln("The", image, "is not present on list of allowed", optionName)
for _, allowedImage := range allowedImages {
s.Println("-", allowedImage)
}
s.Println()
} else {
s.Errorln("No", optionName, "are allowed")
}
s.Println("Please check runner's configuration: http://doc.gitlab.com/ci/builds_configuration/docker.html#overwrite-image-and-services")
return errors.New("invalid image")
}
func (s *DockerExecutor) getImageName() (string, error) {
if imageOption, ok := s.Build.Options["image"].(string); ok && imageOption != "" {
err := s.verifyAllowedImage(imageOption, "images", s.Config.Docker.AllowedImages...)
if err != nil {
return "", err
}
return imageOption, nil
}
return s.Config.Docker.Image, nil
}
func (s *DockerExecutor) Prepare(config *common.RunnerConfig, build *common.Build) error { func (s *DockerExecutor) Prepare(config *common.RunnerConfig, build *common.Build) error {
err := s.AbstractExecutor.Prepare(config, build) err := s.AbstractExecutor.Prepare(config, build)
if err != nil { if err != nil {
...@@ -390,20 +469,24 @@ func (s *DockerExecutor) Prepare(config *common.RunnerConfig, build *common.Buil ...@@ -390,20 +469,24 @@ func (s *DockerExecutor) Prepare(config *common.RunnerConfig, build *common.Buil
return errors.New("Docker doesn't support shells that require script file") return errors.New("Docker doesn't support shells that require script file")
} }
s.Println("Using Docker executor with image", s.Config.Docker.Image, "...")
if config.Docker == nil { if config.Docker == nil {
return errors.New("Missing docker configuration") return errors.New("Missing docker configuration")
} }
imageName, err := s.getImageName()
if err != nil {
return err
}
s.Println("Using Docker executor with image", imageName, "...")
client, err := s.connect() client, err := s.connect()
if err != nil { if err != nil {
return err return err
} }
s.client = client s.client = client
// Get image image, err := s.getDockerImage(imageName)
image, err := s.getImage(s.Config.Docker.Image)
if err != nil { if err != nil {
return err return err
} }
...@@ -425,7 +508,7 @@ func (s *DockerExecutor) Cleanup() { ...@@ -425,7 +508,7 @@ func (s *DockerExecutor) Cleanup() {
} }
func (s *DockerExecutor) waitForServiceContainer(container *docker.Container, timeout time.Duration) error { func (s *DockerExecutor) waitForServiceContainer(container *docker.Container, timeout time.Duration) error {
waitImage, err := s.getImage("aanand/wait") waitImage, err := s.getDockerImage("aanand/wait")
if err != nil { if err != nil {
return err return err
} }
......
...@@ -69,6 +69,7 @@ func init() { ...@@ -69,6 +69,7 @@ func init() {
DefaultShell: "bash", DefaultShell: "bash",
ShellType: common.NormalShell, ShellType: common.NormalShell,
ShowHostname: true, ShowHostname: true,
SupportedOptions: []string{"image", "services"},
} }
common.RegisterExecutor("docker", func() common.Executor { common.RegisterExecutor("docker", func() common.Executor {
......
...@@ -71,6 +71,7 @@ func init() { ...@@ -71,6 +71,7 @@ func init() {
DefaultShell: "bash", DefaultShell: "bash",
ShellType: common.LoginShell, ShellType: common.LoginShell,
ShowHostname: true, ShowHostname: true,
SupportedOptions: []string{"image", "services"},
} }
common.RegisterExecutor("docker-ssh", func() common.Executor { common.RegisterExecutor("docker-ssh", func() common.Executor {
......
...@@ -121,6 +121,10 @@ func (e *AbstractExecutor) Println(args ...interface{}) { ...@@ -121,6 +121,10 @@ func (e *AbstractExecutor) Println(args ...interface{}) {
e.Build.WriteString(fmt.Sprintln(args...)) e.Build.WriteString(fmt.Sprintln(args...))
} }
if len(args) == 0 {
return
}
args = append([]interface{}{e.Config.ShortDescription(), e.Build.ID}, args...) args = append([]interface{}{e.Config.ShortDescription(), e.Build.ID}, args...)
log.Println(args...) log.Println(args...)
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment