diff --git a/pkg/plugin/publish/docker.go b/pkg/plugin/publish/docker.go new file mode 100644 index 000000000..b854d181b --- /dev/null +++ b/pkg/plugin/publish/docker.go @@ -0,0 +1,65 @@ +package publish + +import ( + "fmt" + "strconv" + + "github.com/drone/drone/pkg/build/buildfile" + "github.com/drone/drone/pkg/build/repo" +) + +type Docker struct { + // The path to the dockerfile to create the image from + Dockerfile string `yaml:"docker_file"` + + // Connection information for the docker server that will build the image + Server string `yaml:"docker_server"` + Port int `yaml:"docker_port"` + // The Docker client version to download. This must match the docker version on the server + DockerVersion string `yaml:"docker_version"` + + RepoBaseName string `yaml:"repo_base_name"` + + // Authentication credentials for index.docker.io + Username string `yaml:"username"` + Password string `yaml:"password"` + Email string `yaml:"email"` + + Branch string `yaml:"branch,omitempty"` +} + +// Write adds commands to the buildfile to do the following: +// 1. Install the docker client in the Drone container. +// 2. Build a docker image based on the dockerfile defined in the config. +// 3. Push that docker image to index.docker.io. +// 4. Delete the docker image on the server it was build on so we conserve disk space. +func (d *Docker) Write(f *buildfile.Buildfile, r *repo.Repo) { + if len(d.Dockerfile) == 0 || len(d.Server) == 0 || d.Port == 0 || len(d.DockerVersion) == 0 || + len(d.RepoBaseName) == 0 || len(d.Username) == 0 || len(d.Password) == 0 || + len(d.Email) == 0 { + f.WriteCmdSilent(`echo "Docker Plugin: Missing argument(s)"`) + return + } + + // Install Docker on the container + f.WriteCmd("sudo sh -c \"echo deb http://get.docker.io/ubuntu docker main\\ > " + + "/etc/apt/sources.list.d/docker.list\"") + f.WriteCmd("sudo apt-get update") + f.WriteCmd("sudo apt-get --yes install lxc-docker-" + d.DockerVersion) + + dockerServerUrl := d.Server + ":" + strconv.Itoa(d.Port) + dockerRepo := d.RepoBaseName + "/" + r.Name + // Run the command commands to build and deploy the image. Note that the image is tagged + // with the git hash. + f.WriteCmd(fmt.Sprintf("docker -H %s build -t %s:$(git rev-parse --short HEAD) - < %s", + dockerServerUrl, dockerRepo, d.Dockerfile)) + + // Login and push to index.docker.io + f.WriteCmd(fmt.Sprintf("docker -H %s login -u %s -p %s -e %s", + dockerServerUrl, d.Username, d.Password, d.Email)) + f.WriteCmd(fmt.Sprintf("docker -H %s push %s", dockerServerUrl, dockerRepo)) + + // Delete the image from the docker server we built on. + f.WriteCmd(fmt.Sprintf("docker -H %s rmi %s:$(git rev-parse --short HEAD)", + dockerServerUrl, dockerRepo)) +} diff --git a/pkg/plugin/publish/docker_test.go b/pkg/plugin/publish/docker_test.go new file mode 100644 index 000000000..44271f719 --- /dev/null +++ b/pkg/plugin/publish/docker_test.go @@ -0,0 +1,75 @@ +package publish + +import ( + "strings" + "testing" + + "gopkg.in/v1/yaml" + "github.com/drone/drone/pkg/build/buildfile" + "github.com/drone/drone/pkg/build/repo" +) + +type PublishToDrone struct { + Publish *Publish `yaml:"publish,omitempty"` +} + +func setUpWithDrone(input string) (string, error) { + var buildStruct PublishToDrone + err := yaml.Unmarshal([]byte(input), &buildStruct) + if err != nil { + return "", err + } + bf := buildfile.New() + buildStruct.Publish.Write(bf, &repo.Repo{Name: "name"}) + return bf.String(), err +} + +var missingFieldsYaml = ` +publish: + docker: + dockerfile: file +` + +func TestMissingFields(t *testing.T) { + response, err := setUpWithDrone(missingFieldsYaml) + if err != nil { + t.Fatalf("Can't unmarshal script: %s", err.Error()) + } + if !strings.Contains(response, "echo \"Docker Plugin: Missing argument(s)") { + t.Fatalf("Response: " + response + " didn't contain missing arguments warning") + } +} + +var validYaml = ` +publish: + docker: + docker_file: file_path + docker_server: server + docker_port: 1000 + docker_version: 1.0 + repo_base_name: base_repo + username: user + password: password + email: email +` + +func TestValidYaml(t *testing.T) { + response, err := setUpWithDrone(validYaml) + if err != nil { + t.Fatalf("Can't unmarshal script: %s", err.Error()) + } + if !strings.Contains(response, "docker -H server:1000 build -t base_repo/name" + + ":$(git rev-parse --short HEAD)") { + t.Fatalf("Response: " + response + "doesn't contain build command") + } + if !strings.Contains(response, "docker -H server:1000 login -u user -p password -e email") { + t.Fatalf("Response: " + response + " doesn't contain login command") + } + if !strings.Contains(response, "docker -H server:1000 push base_repo/name") { + t.Fatalf("Response: " + response + " doesn't contain push command") + } + if !strings.Contains(response, "docker -H server:1000 rmi base_repo/name:" + + "$(git rev-parse --short HEAD)") { + t.Fatalf("Response: " + response + " doesn't contain remove image command") + } +} diff --git a/pkg/plugin/publish/publish.go b/pkg/plugin/publish/publish.go index 546be583e..d9bdbf928 100644 --- a/pkg/plugin/publish/publish.go +++ b/pkg/plugin/publish/publish.go @@ -9,10 +9,11 @@ import ( // for publishing build artifacts when // a Build has succeeded type Publish struct { - S3 *S3 `yaml:"s3,omitempty"` - Swift *Swift `yaml:"swift,omitempty"` - PyPI *PyPI `yaml:"pypi,omitempty"` - NPM *NPM `yaml:"npm,omitempty"` + S3 *S3 `yaml:"s3,omitempty"` + Swift *Swift `yaml:"swift,omitempty"` + PyPI *PyPI `yaml:"pypi,omitempty"` + NPM *NPM `yaml:"npm,omitempty"` + Docker *Docker `yaml:"docker,omitempty"` } func (p *Publish) Write(f *buildfile.Buildfile, r *repo.Repo) { @@ -35,4 +36,9 @@ func (p *Publish) Write(f *buildfile.Buildfile, r *repo.Repo) { if p.NPM != nil && (len(p.NPM.Branch) == 0 || (len(p.NPM.Branch) > 0 && r.Branch == p.NPM.Branch)) { p.NPM.Write(f) } + + // Docker + if p.Docker != nil && (len(p.Docker.Branch) == 0 || (len(p.Docker.Branch) > 0 && r.Branch == p.Docker.Branch)) { + p.Docker.Write(f, r) + } }