targets/windows/handle_container.go (177 lines of code) (raw):
package windows
import (
"context"
"encoding/json"
"fmt"
"path"
"runtime"
"sort"
"sync"
"github.com/Azure/dalec"
"github.com/Azure/dalec/frontend"
"github.com/containerd/platforms"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/client/llb/sourceresolver"
"github.com/moby/buildkit/frontend/dockerui"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)
const (
defaultBaseImage = "mcr.microsoft.com/windows/nanoserver:1809"
windowsSystemDir = "/Windows/System32/"
)
var (
defaultPlatform = ocispecs.Platform{
OS: outputKey,
// NOTE: Windows is (currently) only supported on amd64.
// Making this use runtime.GOARCH so that builds are more explicitly and not surprising.
// If/when Windows is supported on another platform (ie arm64) this will work as expected.
// Until then, if someone really wants to build an amd64 image from arm64 they'll need to set the platform explicitly in the build request.
Architecture: runtime.GOARCH,
}
)
func handleContainer(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) {
dc, err := dockerui.NewClient(client)
if err != nil {
return nil, err
}
if len(dc.TargetPlatforms) > 1 {
return nil, fmt.Errorf("multi-platform output is not supported")
}
sOpt := frontend.SourceOptFromUIClient(ctx, client, dc, nil)
spec, err := frontend.LoadSpec(ctx, dc, nil)
if err != nil {
return nil, err
}
targetKey := frontend.GetTargetKey(client)
bases := spec.GetImageBases(targetKey)
if len(bases) == 0 {
bases = append(bases, dalec.BaseImage{
Rootfs: dalec.Source{
DockerImage: &dalec.SourceDockerImage{Ref: defaultBaseImage},
},
})
}
eg, grpCtx := errgroup.WithContext(ctx)
var mu sync.Mutex
cfgs := make([][]byte, len(bases))
targets := make([]ocispecs.Platform, len(cfgs))
basePlatform := defaultPlatform
if len(dc.TargetPlatforms) > 0 {
basePlatform = dc.TargetPlatforms[0]
}
for idx, bi := range bases {
idx := idx
bi := bi
eg.Go(func() error {
dt, err := bi.ResolveImageConfig(grpCtx, sOpt, sourceresolver.Opt{
Platform: &basePlatform,
ImageOpt: &sourceresolver.ResolveImageOpt{
ResolveMode: dc.ImageResolveMode.String(),
},
})
if err != nil {
return err
}
var cfg dalec.DockerImageSpec
if err := json.Unmarshal(dt, &cfg); err != nil {
return errors.Wrapf(err, "error unmarshalling base image config for base image at index %d", idx)
}
mu.Lock()
cfgs[idx] = dt
targets[idx] = cfg.Platform
mu.Unlock()
return nil
})
}
if err := eg.Wait(); err != nil {
return nil, err
}
seen := make(map[string]struct{})
for _, p := range targets {
s := platforms.FormatAll(p)
if _, ok := seen[s]; ok {
return nil, fmt.Errorf("multiple base images provided with the same platform value")
}
seen[s] = struct{}{}
}
dc.TargetPlatforms = targets
if len(targets) > 1 {
dc.MultiPlatformRequested = true
}
rb, err := dc.Build(ctx, func(ctx context.Context, platform *ocispecs.Platform, idx int) (ref gwclient.Reference, retCfg, retBaseCfg *dalec.DockerImageSpec, retErr error) {
spec, err := frontend.LoadSpec(ctx, dc, platform)
if err != nil {
return nil, nil, nil, err
}
if err := validateRuntimeDeps(spec, targetKey); err != nil {
return nil, nil, nil, fmt.Errorf("error validating windows spec: %w", err)
}
pg := dalec.ProgressGroup("Build windows container: " + spec.Name)
worker, err := distroConfig.Worker(sOpt, pg)
if err != nil {
return nil, nil, nil, err
}
bin, err := buildBinaries(ctx, spec, worker, client, sOpt, targetKey)
if err != nil {
return nil, nil, nil, fmt.Errorf("unable to build binary %w", err)
}
bi := bases[idx]
if platform == nil {
platform = &defaultPlatform
}
baseImage, err := bi.ToState(sOpt, pg, llb.Platform(*platform))
if err != nil {
return nil, nil, nil, err
}
out := baseImage.
File(llb.Copy(bin, "/", windowsSystemDir)).
With(copySymlinks(spec.GetImagePost(targetKey)))
def, err := out.Marshal(ctx)
if err != nil {
return nil, nil, nil, err
}
res, err := client.Solve(ctx, gwclient.SolveRequest{
Definition: def.ToPB(),
})
if err != nil {
return nil, nil, nil, err
}
dt := cfgs[idx]
var baseCfg dalec.DockerImageSpec
if err := json.Unmarshal(cfgs[idx], &baseCfg); err != nil {
return nil, nil, nil, errors.Wrap(err, "error unmarshalling base image config")
}
var img dalec.DockerImageSpec
if err := json.Unmarshal(dt, &img); err != nil {
return nil, nil, nil, errors.Wrap(err, "error unmarshalling base image config")
}
if err := dalec.BuildImageConfig(spec, targetKey, &img); err != nil {
return nil, nil, nil, errors.Wrap(err, "error creating image config")
}
ref, err = res.SingleRef()
return ref, &img, &baseCfg, err
})
if err != nil {
return nil, err
}
return rb.Finalize()
}
func copySymlinks(post *dalec.PostInstall) llb.StateOption {
return func(s llb.State) llb.State {
if post == nil {
return s
}
if len(post.Symlinks) == 0 {
return s
}
sortedKeys := dalec.SortMapKeys(post.Symlinks)
for _, oldpath := range sortedKeys {
newpaths := post.Symlinks[oldpath].Paths
sort.Strings(newpaths)
for _, newpath := range newpaths {
s = s.File(llb.Mkdir(path.Dir(newpath), 0755, llb.WithParents(true)))
s = s.File(llb.Copy(s, oldpath, newpath))
}
}
return s
}
}