Files
indra/pkg/docker/builder.go
2023-08-10 18:48:59 +01:00

220 lines
4.6 KiB
Go

package docker
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"io"
"io/ioutil"
"os"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/moby/term"
log2 "git.indra-labs.org/dev/ind/pkg/proc/log"
)
var (
log = log2.GetLogger()
check = log.E.Chk
)
var (
isRelease = false
isPushable = false
)
// SetRelease enables releasing of docker images.
func SetRelease() {
isRelease = true
}
// SetPush enables pushing of dockre images.
func SetPush() {
isPushable = true
}
// Builder is a data structure defining a docker build.
type Builder struct {
*client.Client
ctx context.Context
configs []BuildConfiguration
source_configs []BuildConfiguration
pkg_configs []BuildConfiguration
}
func (b *Builder) build(buildConfig BuildConfiguration) (err error) {
// We need the absolute path for build tags to be valid
buildConfig.BuildOpts.Tags = buildConfig.FixTagPrefix()
log.I.Ln("building", buildConfig.BuildOpts.Tags[0], "from", buildConfig.BuildOpts.Dockerfile)
// If we're building a release, we should also tag stable.
if isRelease {
buildConfig.BuildOpts.Tags = append(buildConfig.BuildOpts.Tags, "stable")
}
// Generate a tar file for docker's release context. It will contain the root of the repository's path.
// A tar file is passed in to the docker daemon.
var tar io.ReadCloser
if tar, err = archive.TarWithOptions(".", &archive.TarOptions{}); check(err) {
return
}
defer tar.Close()
// Submit a release to docker; with the context tar, and default options defined above.
log.I.Ln("running build with docker...")
var response types.ImageBuildResponse
if response, err = b.ImageBuild(b.ctx, tar, buildConfig.BuildOpts); check(err) {
return
}
defer response.Body.Close()
// Generate a terminal for output
termFd, isTerm := term.GetFdInfo(os.Stderr)
if err = jsonmessage.DisplayJSONMessagesStream(response.Body, os.Stderr, termFd, isTerm, nil); check(err) {
return
}
// Prune the intermediate golang:x.xx builder container
log.I.Ln("pruning release container(s)...")
if _, err = b.ImagesPrune(b.ctx, filters.NewArgs()); check(err) {
return
}
log.I.Ln("pruning successful.")
log.I.Ln("release successful!")
return
}
// Build runs a Builder specification.
func (b *Builder) Build() (err error) {
for _, buildConfig := range b.source_configs {
if err = b.build(buildConfig); check(err) {
return
}
}
for _, buildConfig := range b.configs {
if err = b.build(buildConfig); check(err) {
return
}
}
for _, buildConfig := range b.pkg_configs {
if err = b.build(buildConfig); check(err) {
return
}
}
return nil
}
func (b *Builder) push(buildConfig BuildConfiguration) (err error) {
if !isPushable {
return nil
}
log.I.Ln("pushing tagged images to repository...")
// Load the docker config
var file []byte
var config *configfile.ConfigFile
if file, err = ioutil.ReadFile(os.Getenv("INDRA_DOCKER_CONFIG")); check(err) {
return
}
config = configfile.New("config.json")
config.LoadFromReader(bytes.NewReader(file))
// Generate a terminal for output
termFd, isTerm := term.GetFdInfo(os.Stderr)
// Push the specified tags to each docker repository
var pushResponse io.ReadCloser
for _, auth := range config.AuthConfigs {
log.I.Ln("found", auth.ServerAddress)
// Generate an authentication token
authConfigBytes, _ := json.Marshal(auth)
buildConfig.PushOpts.RegistryAuth = base64.URLEncoding.EncodeToString(authConfigBytes)
// Pushes each tag to the docker repository.
for _, tag := range buildConfig.FixTagPrefix() {
log.I.Ln("pushing", tag)
if pushResponse, err = b.ImagePush(b.ctx, tag, buildConfig.PushOpts); check(err) {
return
}
if err = jsonmessage.DisplayJSONMessagesStream(pushResponse, os.Stderr, termFd, isTerm, nil); check(err) {
return
}
if err = pushResponse.Close(); check(err) {
return
}
}
}
log.I.Ln("sucessfully pushed!")
return nil
}
// Push a build to the configured docker registry/repository.
func (b *Builder) Push() (err error) {
for _, buildConfig := range b.configs {
if err = b.push(buildConfig); check(err) {
return
}
}
return nil
}
// NewBuilder bundles a builder together from its parts.
func NewBuilder(ctx context.Context, cli *client.Client, sourceConfigs []BuildConfiguration,
buildConfigs []BuildConfiguration, pkgConfigs []BuildConfiguration) (builder *Builder) {
return &Builder{cli, ctx, buildConfigs, sourceConfigs, pkgConfigs}
}