mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
ae4063585e
Signed-off-by: David Calavera <david.calavera@gmail.com>
243 lines
5.9 KiB
Go
243 lines
5.9 KiB
Go
package builder
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/docker/docker/api"
|
|
"github.com/docker/docker/builder/parser"
|
|
"github.com/docker/docker/daemon"
|
|
"github.com/docker/docker/engine"
|
|
"github.com/docker/docker/graph"
|
|
"github.com/docker/docker/pkg/archive"
|
|
"github.com/docker/docker/pkg/httputils"
|
|
"github.com/docker/docker/pkg/parsers"
|
|
"github.com/docker/docker/pkg/streamformatter"
|
|
"github.com/docker/docker/pkg/urlutil"
|
|
"github.com/docker/docker/registry"
|
|
"github.com/docker/docker/runconfig"
|
|
)
|
|
|
|
// whitelist of commands allowed for a commit/import
|
|
var validCommitCommands = map[string]bool{
|
|
"entrypoint": true,
|
|
"cmd": true,
|
|
"user": true,
|
|
"workdir": true,
|
|
"env": true,
|
|
"volume": true,
|
|
"expose": true,
|
|
"onbuild": true,
|
|
}
|
|
|
|
type Config struct {
|
|
DockerfileName string
|
|
RemoteURL string
|
|
RepoName string
|
|
SuppressOutput bool
|
|
NoCache bool
|
|
Remove bool
|
|
ForceRemove bool
|
|
Pull bool
|
|
JSONFormat bool
|
|
Memory int64
|
|
MemorySwap int64
|
|
CpuShares int64
|
|
CpuSetCpus string
|
|
CpuSetMems string
|
|
AuthConfig *registry.AuthConfig
|
|
ConfigFile *registry.ConfigFile
|
|
|
|
Stdout io.Writer
|
|
Context io.ReadCloser
|
|
// When closed, the job has been cancelled.
|
|
// Note: not all jobs implement cancellation.
|
|
// See Job.Cancel() and Job.WaitCancelled()
|
|
cancelled chan struct{}
|
|
cancelOnce sync.Once
|
|
}
|
|
|
|
// When called, causes the Job.WaitCancelled channel to unblock.
|
|
func (b *Config) Cancel() {
|
|
b.cancelOnce.Do(func() {
|
|
close(b.cancelled)
|
|
})
|
|
}
|
|
|
|
// Returns a channel which is closed ("never blocks") when the job is cancelled.
|
|
func (b *Config) WaitCancelled() <-chan struct{} {
|
|
return b.cancelled
|
|
}
|
|
|
|
func NewBuildConfig() *Config {
|
|
return &Config{
|
|
AuthConfig: ®istry.AuthConfig{},
|
|
ConfigFile: ®istry.ConfigFile{},
|
|
cancelled: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func Build(d *daemon.Daemon, e *engine.Engine, buildConfig *Config) error {
|
|
var (
|
|
repoName string
|
|
tag string
|
|
context io.ReadCloser
|
|
)
|
|
|
|
repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName)
|
|
if repoName != "" {
|
|
if err := registry.ValidateRepositoryName(repoName); err != nil {
|
|
return err
|
|
}
|
|
if len(tag) > 0 {
|
|
if err := graph.ValidateTagName(tag); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if buildConfig.RemoteURL == "" {
|
|
context = ioutil.NopCloser(buildConfig.Context)
|
|
} else if urlutil.IsGitURL(buildConfig.RemoteURL) {
|
|
if !urlutil.IsGitTransport(buildConfig.RemoteURL) {
|
|
buildConfig.RemoteURL = "https://" + buildConfig.RemoteURL
|
|
}
|
|
root, err := ioutil.TempDir("", "docker-build-git")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer os.RemoveAll(root)
|
|
|
|
if output, err := exec.Command("git", "clone", "--recursive", buildConfig.RemoteURL, root).CombinedOutput(); err != nil {
|
|
return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
|
|
}
|
|
|
|
c, err := archive.Tar(root, archive.Uncompressed)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
context = c
|
|
} else if urlutil.IsURL(buildConfig.RemoteURL) {
|
|
f, err := httputils.Download(buildConfig.RemoteURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Body.Close()
|
|
dockerFile, err := ioutil.ReadAll(f.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// When we're downloading just a Dockerfile put it in
|
|
// the default name - don't allow the client to move/specify it
|
|
buildConfig.DockerfileName = api.DefaultDockerfileName
|
|
|
|
c, err := archive.Generate(buildConfig.DockerfileName, string(dockerFile))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
context = c
|
|
}
|
|
defer context.Close()
|
|
|
|
sf := streamformatter.NewStreamFormatter(buildConfig.JSONFormat)
|
|
|
|
builder := &Builder{
|
|
Daemon: d,
|
|
Engine: e,
|
|
OutStream: &streamformatter.StdoutFormater{
|
|
Writer: buildConfig.Stdout,
|
|
StreamFormatter: sf,
|
|
},
|
|
ErrStream: &streamformatter.StderrFormater{
|
|
Writer: buildConfig.Stdout,
|
|
StreamFormatter: sf,
|
|
},
|
|
Verbose: !buildConfig.SuppressOutput,
|
|
UtilizeCache: !buildConfig.NoCache,
|
|
Remove: buildConfig.Remove,
|
|
ForceRemove: buildConfig.ForceRemove,
|
|
Pull: buildConfig.Pull,
|
|
OutOld: buildConfig.Stdout,
|
|
StreamFormatter: sf,
|
|
AuthConfig: buildConfig.AuthConfig,
|
|
ConfigFile: buildConfig.ConfigFile,
|
|
dockerfileName: buildConfig.DockerfileName,
|
|
cpuShares: buildConfig.CpuShares,
|
|
cpuSetCpus: buildConfig.CpuSetCpus,
|
|
cpuSetMems: buildConfig.CpuSetMems,
|
|
memory: buildConfig.Memory,
|
|
memorySwap: buildConfig.MemorySwap,
|
|
cancelled: buildConfig.WaitCancelled(),
|
|
}
|
|
|
|
id, err := builder.Run(context)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if repoName != "" {
|
|
return d.Repositories().Tag(repoName, tag, id, true)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func BuildFromConfig(d *daemon.Daemon, e *engine.Engine, c *runconfig.Config, changes []string) (*runconfig.Config, error) {
|
|
ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// ensure that the commands are valid
|
|
for _, n := range ast.Children {
|
|
if !validCommitCommands[n.Value] {
|
|
return nil, fmt.Errorf("%s is not a valid change command", n.Value)
|
|
}
|
|
}
|
|
|
|
builder := &Builder{
|
|
Daemon: d,
|
|
Engine: e,
|
|
Config: c,
|
|
OutStream: ioutil.Discard,
|
|
ErrStream: ioutil.Discard,
|
|
disableCommit: true,
|
|
}
|
|
|
|
for i, n := range ast.Children {
|
|
if err := builder.dispatch(i, n); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return builder.Config, nil
|
|
}
|
|
|
|
func Commit(d *daemon.Daemon, eng *engine.Engine, name string, c *daemon.ContainerCommitConfig) (string, error) {
|
|
container, err := d.Get(name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
newConfig, err := BuildFromConfig(d, eng, c.Config, c.Changes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err := runconfig.Merge(newConfig, container.Config); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
img, err := d.Commit(container, c.Repo, c.Tag, c.Comment, c.Author, c.Pause, newConfig)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return img.ID, nil
|
|
}
|