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:
commit
e52a6de9ba
@ -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:
|
||||
|
@ -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
4
Dockerfile
Normal file
@ -0,0 +1,4 @@
|
||||
FROM scratch
|
||||
COPY goreleaser /goreleaser
|
||||
ENTRYPOINT ["/goreleaser"]
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
3
config/testdata/invalid_config.yml
vendored
3
config/testdata/invalid_config.yml
vendored
@ -27,4 +27,5 @@ fpm:
|
||||
invalid_fpm: 1
|
||||
snapshot:
|
||||
invalid_snapshot: 1
|
||||
|
||||
dockers:
|
||||
- invalid_docker: 1
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
108
pipeline/docker/docker.go
Normal 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
|
||||
}
|
110
pipeline/docker/docker_test.go
Normal file
110
pipeline/docker/docker_test.go
Normal 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
2
pipeline/docker/testdata/Dockerfile
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
FROM scratch
|
||||
ADD mybin /
|
@ -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
|
||||
|
@ -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")))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user