1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-19 20:57:53 +02:00

Merge pull request #355 from goreleaser/docker

draft: Docker integration
This commit is contained in:
Carlos Alexandro Becker 2017-09-12 21:41:25 -03:00 committed by GitHub
commit e52a6de9ba
14 changed files with 290 additions and 5 deletions

View File

@ -13,6 +13,8 @@ builds:
- arm64
checksum:
name_template: '{{ .ProjectName }}_checksums.txt'
dockers:
- image: goreleaser/goreleaser
archive:
name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
replacements:

View File

@ -2,8 +2,11 @@ dist: trusty
sudo: required
language: go
go: 1.8.3
services:
- docker
install:
- make setup
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
- gem install fpm
- sudo apt-get update
- sudo apt-get install --yes snapd rpm

4
Dockerfile Normal file
View File

@ -0,0 +1,4 @@
FROM scratch
COPY goreleaser /goreleaser
ENTRYPOINT ["/goreleaser"]

View File

@ -159,6 +159,19 @@ type Checksum struct {
XXX map[string]interface{} `yaml:",inline"`
}
// Docker image config
type Docker struct {
Binary string `yaml:",omitempty"`
Goos string `yaml:",omitempty"`
Goarch string `yaml:",omitempty"`
Goarm string `yaml:",omitempty"`
Image string `yaml:",omitempty"`
Dockerfile string `yaml:",omitempty"`
// Capture all undefined fields and should be empty after loading
XXX map[string]interface{} `yaml:",inline"`
}
// Project includes all project configuration
type Project struct {
ProjectName string `yaml:"project_name,omitempty"`
@ -170,6 +183,7 @@ type Project struct {
Snapcraft Snapcraft `yaml:",omitempty"`
Snapshot Snapshot `yaml:",omitempty"`
Checksum Checksum `yaml:",omitempty"`
Dockers []Docker `yaml:",omitempty"`
// this is a hack ¯\_(ツ)_/¯
SingleBuild Build `yaml:"build,omitempty"`
@ -231,6 +245,9 @@ func checkOverflows(config Project) error {
}
overflow.check(config.Snapshot.XXX, "snapshot")
overflow.check(config.Checksum.XXX, "checksum")
for i, docker := range config.Dockers {
overflow.check(docker.XXX, fmt.Sprintf("docker[%d]", i))
}
return overflow.err()
}

View File

@ -58,7 +58,7 @@ func TestFileNotFound(t *testing.T) {
func TestInvalidFields(t *testing.T) {
var assert = assert.New(t)
_, err := Load("testdata/invalid_config.yml")
assert.EqualError(err, "unknown fields in the config file: invalid_root, archive.invalid_archive, archive.format_overrides[0].invalid_archive_fmtoverrides, brew.invalid_brew, brew.github.invalid_brew_github, builds[0].invalid_builds, builds[0].hooks.invalid_builds_hooks, builds[0].ignored_builds[0].invalid_builds_ignore, fpm.invalid_fpm, release.invalid_release, release.github.invalid_release_github, build.invalid_build, builds.hooks.invalid_build_hook, builds.ignored_builds[0].invalid_build_ignore, snapshot.invalid_snapshot")
assert.EqualError(err, "unknown fields in the config file: invalid_root, archive.invalid_archive, archive.format_overrides[0].invalid_archive_fmtoverrides, brew.invalid_brew, brew.github.invalid_brew_github, builds[0].invalid_builds, builds[0].hooks.invalid_builds_hooks, builds[0].ignored_builds[0].invalid_builds_ignore, fpm.invalid_fpm, release.invalid_release, release.github.invalid_release_github, build.invalid_build, builds.hooks.invalid_build_hook, builds.ignored_builds[0].invalid_build_ignore, snapshot.invalid_snapshot, docker[0].invalid_docker")
}
func TestInvalidYaml(t *testing.T) {

View File

@ -27,4 +27,5 @@ fpm:
invalid_fpm: 1
snapshot:
invalid_snapshot: 1
dockers:
- invalid_docker: 1

View File

@ -16,6 +16,7 @@ import (
"github.com/goreleaser/goreleaser/pipeline/checksums"
"github.com/goreleaser/goreleaser/pipeline/cleandist"
"github.com/goreleaser/goreleaser/pipeline/defaults"
"github.com/goreleaser/goreleaser/pipeline/docker"
"github.com/goreleaser/goreleaser/pipeline/env"
"github.com/goreleaser/goreleaser/pipeline/fpm"
"github.com/goreleaser/goreleaser/pipeline/git"
@ -35,6 +36,7 @@ var pipes = []pipeline.Pipe{
snapcraft.Pipe{}, // archive via snapcraft (snap)
checksums.Pipe{}, // checksums of the files
release.Pipe{}, // release to github
docker.Pipe{}, // create and push docker images
brew.Pipe{}, // push to brew tap
}
@ -96,9 +98,8 @@ func handle(err error) error {
if err == nil {
return nil
}
skip, ok := err.(pipeline.ErrSkip)
if ok {
log.WithField("reason", skip.Error()).Warn("skipped")
if pipeline.IsSkip(err) {
log.WithField("reason", err.Error()).Warn("skipped")
return nil
}
return err

View File

@ -57,6 +57,21 @@ func (Pipe) Run(ctx *context.Context) error {
}
ctx.Config.Brew.Install = strings.Join(installs, "\n")
}
if len(ctx.Config.Dockers) == 1 {
if ctx.Config.Dockers[0].Goos == "" {
ctx.Config.Dockers[0].Goos = "linux"
}
if ctx.Config.Dockers[0].Goarch == "" {
ctx.Config.Dockers[0].Goarch = "amd64"
}
if ctx.Config.Dockers[0].Binary == "" {
ctx.Config.Dockers[0].Binary = ctx.Config.Builds[0].Binary
}
if ctx.Config.Dockers[0].Dockerfile == "" {
ctx.Config.Dockers[0].Dockerfile = "Dockerfile"
}
}
err := setArchiveDefaults(ctx)
log.WithField("config", ctx.Config).Debug("defaults set")
return err

View File

@ -32,6 +32,7 @@ func TestFillBasicData(t *testing.T) {
assert.Contains(ctx.Config.Builds[0].Goarch, "amd64")
assert.Equal("tar.gz", ctx.Config.Archive.Format)
assert.Contains(ctx.Config.Brew.Install, "bin.install \"goreleaser\"")
assert.Empty(ctx.Config.Dockers)
assert.NotEmpty(
ctx.Config.Archive.NameTemplate,
ctx.Config.Builds[0].Ldflags,
@ -65,11 +66,19 @@ func TestFillPartial(t *testing.T) {
},
},
},
Dockers: []config.Docker{
{Image: "a/b"},
},
},
}
assert.NoError(Pipe{}.Run(ctx))
assert.Len(ctx.Config.Archive.Files, 1)
assert.Equal(`bin.install "testreleaser"`, ctx.Config.Brew.Install)
assert.NotEmpty(ctx.Config.Dockers[0].Binary)
assert.NotEmpty(ctx.Config.Dockers[0].Goos)
assert.NotEmpty(ctx.Config.Dockers[0].Goarch)
assert.NotEmpty(ctx.Config.Dockers[0].Dockerfile)
assert.Empty(ctx.Config.Dockers[0].Goarm)
}
func TestFillSingleBuild(t *testing.T) {

108
pipeline/docker/docker.go Normal file
View File

@ -0,0 +1,108 @@
// Package docker provides a Pipe that creates and pushes a Docker image
package docker
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/goreleaser/goreleaser/config"
"github.com/goreleaser/goreleaser/context"
"github.com/goreleaser/goreleaser/pipeline"
"github.com/apex/log"
"github.com/pkg/errors"
)
// ErrNoDocker is shown when docker cannot be found in $PATH
var ErrNoDocker = errors.New("docker not present in $PATH")
// Pipe for docker
type Pipe struct{}
// Description of the pipe
func (Pipe) Description() string {
return "Creating Docker images"
}
// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
if len(ctx.Config.Dockers) == 0 || ctx.Config.Dockers[0].Image == "" {
return pipeline.Skip("docker section is not configured")
}
_, err := exec.LookPath("docker")
if err != nil {
return ErrNoDocker
}
for _, docker := range ctx.Config.Dockers {
var imagePlatform = docker.Goos + docker.Goarch + docker.Goarm
for platform, groups := range ctx.Binaries {
if platform != imagePlatform {
continue
}
for folder, binaries := range groups {
for _, binary := range binaries {
if binary.Name != docker.Binary {
continue
}
var err = doRun(ctx, folder, docker, binary)
if err != nil && !pipeline.IsSkip(err) {
return err
}
}
}
}
}
return nil
}
func doRun(ctx *context.Context, folder string, docker config.Docker, binary context.Binary) error {
var root = filepath.Join(ctx.Config.Dist, folder)
var dockerfile = filepath.Join(root, filepath.Base(docker.Dockerfile))
var image = fmt.Sprintf("%s:%s", docker.Image, ctx.Git.CurrentTag)
if err := os.Link(docker.Dockerfile, dockerfile); err != nil {
return errors.Wrap(err, "failed to link dockerfile")
}
if err := dockerBuild(root, dockerfile, image); err != nil {
return err
}
// TODO: improve this so it can log into to stdout
if !ctx.Publish {
return pipeline.Skip("--skip-publish is set")
}
if ctx.Config.Release.Draft {
return pipeline.Skip("release is marked as draft")
}
if err := dockerPush(image); err != nil {
return err
}
return nil
}
func dockerBuild(root, dockerfile, image string) error {
log.WithField("image", image).Info("building docker image")
var cmd = exec.Command("docker", "build", "-f", dockerfile, "-t", image, root)
log.WithField("cmd", cmd).Debug("executing")
out, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrapf(err, "failed to build docker image: \n%s", string(out))
}
log.Debugf("docker build output: \n%s", string(out))
return nil
}
func dockerPush(image string) error {
log.WithField("image", image).Info("pushing docker image")
var cmd = exec.Command("docker", "push", image)
log.WithField("cmd", cmd).Debug("executing")
out, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrapf(err, "failed to push docker image: \n%s", string(out))
}
log.Debugf("docker push output: \n%s", string(out))
return nil
}

View File

@ -0,0 +1,110 @@
package docker
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/goreleaser/goreleaser/config"
"github.com/goreleaser/goreleaser/context"
"github.com/goreleaser/goreleaser/pipeline"
"github.com/stretchr/testify/assert"
)
func TestRunPipe(t *testing.T) {
var assert = assert.New(t)
folder, err := ioutil.TempDir("", "archivetest")
assert.NoError(err)
var dist = filepath.Join(folder, "dist")
assert.NoError(os.Mkdir(dist, 0755))
assert.NoError(os.Mkdir(filepath.Join(dist, "mybin"), 0755))
var binPath = filepath.Join(dist, "mybin", "mybin")
_, err = os.Create(binPath)
assert.NoError(err)
// this might fail as the image doesnt exist yet, so lets ignore the error
_ = exec.Command("docker", "rmi", "goreleaser/test_run_pipe:v1.0.0").Run()
var ctx = &context.Context{
Git: context.GitInfo{
CurrentTag: "v1.0.0",
},
Publish: true,
Config: config.Project{
ProjectName: "mybin",
Dist: dist,
Dockers: []config.Docker{
{
Image: "goreleaser/test_run_pipe",
Goos: "linux",
Goarch: "amd64",
Dockerfile: "testdata/Dockerfile",
Binary: "mybin",
},
{
Image: "goreleaser/test_run_pipe_nope",
Goos: "linux",
Goarch: "amd64",
Dockerfile: "testdata/Dockerfile",
Binary: "otherbin",
},
},
},
}
for _, plat := range []string{"linuxamd64", "linux386", "darwinamd64"} {
ctx.AddBinary(plat, "mybin", "mybin", binPath)
}
assert.NoError(Pipe{}.Run(ctx))
// this might should not fail as the image should have been created when
// the step ran
assert.NoError(
exec.Command("docker", "rmi", "goreleaser/test_run_pipe:v1.0.0").Run(),
)
// the test_run_pipe_nope image should not have been created, so deleting
// it should fail
assert.Error(
exec.Command("docker", "rmi", "goreleaser/test_run_pipe_nope:v1.0.0").Run(),
)
}
func TestDescription(t *testing.T) {
var assert = assert.New(t)
assert.NotEmpty(Pipe{}.Description())
}
func TestNoDockers(t *testing.T) {
var assert = assert.New(t)
assert.True(pipeline.IsSkip(Pipe{}.Run(context.New(config.Project{}))))
}
func TestNoDockerWithoutImageName(t *testing.T) {
var assert = assert.New(t)
assert.True(pipeline.IsSkip(Pipe{}.Run(context.New(config.Project{
Dockers: []config.Docker{
{
Goos: "linux",
},
},
}))))
}
func TestDockerNotInPath(t *testing.T) {
var assert = assert.New(t)
var path = os.Getenv("PATH")
defer func() {
assert.NoError(os.Setenv("PATH", path))
}()
assert.NoError(os.Setenv("PATH", ""))
var ctx = &context.Context{
Version: "1.0.0",
Config: config.Project{
Dockers: []config.Docker{
{
Image: "a/b",
},
},
},
}
assert.EqualError(Pipe{}.Run(ctx), ErrNoDocker.Error())
}

2
pipeline/docker/testdata/Dockerfile vendored Normal file
View File

@ -0,0 +1,2 @@
FROM scratch
ADD mybin /

View File

@ -12,6 +12,12 @@ type Pipe interface {
Run(ctx *context.Context) error
}
// IsSkip returns true if the error is an ErrSkip
func IsSkip(err error) bool {
_, ok := err.(ErrSkip)
return ok
}
// ErrSkip occurs when a pipe is skipped for some reason
type ErrSkip struct {
reason string

View File

@ -1,6 +1,7 @@
package pipeline
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
@ -13,3 +14,9 @@ func TestSkipPipe(t *testing.T) {
assert.Error(err)
assert.Equal(reason, err.Error())
}
func TestIsSkip(t *testing.T) {
var assert = assert.New(t)
assert.True(IsSkip(Skip("whatever")))
assert.False(IsSkip(errors.New("nope")))
}