blob: 4cfb52cb2f0b0f9c83c5ea2f4ffb506380fa10c5 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"io"
"github.com/maruel/subcommands"
"go.chromium.org/luci/common/clock"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
"infra/cmd/cloudbuildhelper/docker"
"infra/cmd/cloudbuildhelper/fileset"
)
var cmdLocalBuild = &subcommands.Command{
UsageLine: "localbuild <target-manifest-path> [...]",
ShortDesc: "builds a docker image using local docker daemon",
LongDesc: `Builds a docker image using local docker daemon.
Roughly does "docker build --no-cache --squash --tag <name> <context>", where
<name> and <context> come from the manifest.
Doesn't upload the image anywhere.
`,
CommandRun: func() subcommands.CommandRun {
c := &cmdLocalBuildRun{}
c.init()
return c
},
}
type cmdLocalBuildRun struct {
commandBase
targetManifest string
}
func (c *cmdLocalBuildRun) init() {
c.commandBase.init(c.exec, extraFlags{labels: true}, []*string{
&c.targetManifest,
})
}
func (c *cmdLocalBuildRun) exec(ctx context.Context) error {
m, _, err := c.loadManifest(c.targetManifest, false, false)
if err != nil {
return err
}
labels := docker.Labels{
Created: clock.Now(ctx).UTC(),
BuildTool: UserAgent,
BuildMode: "local",
Extra: c.labels,
}
return stage(ctx, m, func(out *fileset.Set) error {
logging.Infof(ctx, "Sending tarball with %d files to the docker...", out.Len())
r, w := io.Pipe()
var ctxDigest string // sha256 of the context.tar.gz, FYI
var tarErr error // error when writing the tarball
// Feed the tarball to the docker daemon.
done := make(chan struct{})
go func() {
defer close(done)
ctxDigest, tarErr = sendAsTarball(out, w)
}()
imageDigest, dockerErr := docker.Build(ctx, r, append([]string{
"--no-cache",
"--tag", m.Name, // attach a local tag for convenience
}, labels.AsBuildArgs()...))
r.Close()
<-done
switch {
case dockerErr != nil:
return errors.Annotate(dockerErr, "failed to build the image").Err()
case tarErr != nil:
return errors.Annotate(tarErr, "building the image").Err()
}
// TODO(vadimsh): Add -json-output support.
logging.Infof(ctx, "Context: %s", ctxDigest)
logging.Infof(ctx, "Image ID: %s", imageDigest)
return nil
})
}
func sendAsTarball(out *fileset.Set, w io.WriteCloser) (digest string, err error) {
h := sha256.New()
if err := out.ToTarGz(io.MultiWriter(w, h)); err != nil {
w.Close()
return "", errors.Annotate(err, "failed to write the tarball").Err()
}
if err := w.Close(); err != nil {
return "", errors.Annotate(err, "failed to close the write end of the pipe").Err()
}
return hex.EncodeToString(h.Sum(nil)), nil
}