Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions pkg/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,19 @@ func Build(ctx context.Context, opts *BOpts) error {
for k, v := range opts.Labels {
solveOpt.FrontendAttrs["label:"+k] = v
}
for name, ref := range opts.BuildContexts {
switch strings.SplitN(ref, ":", 2)[0] {
case "docker-image", "git", "http", "https":
solveOpt.FrontendAttrs["context:"+name] = ref
case "oci-layout":
// oci-layout requires custom handling as it needs to load the layout data from the client
// not setting solveOpt.FrontendAttrs["context:"+name] here for the frontend can handle it because namedcontext will resolve
solveOpt.OCIStores[name] = opts.ContentStore
default: // bare path → local context
solveOpt.FrontendAttrs["context:"+name] = "local:" + name
}
}

solveOpt.Frontend = "dockerfile.v1"

if len(opts.SSH) > 0 {
Expand Down
13 changes: 9 additions & 4 deletions pkg/build/buildopts.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ const (
KeyProgress = "progress"
// When present, disables layer caching.
KeyNoCache = "no-cache"
// Build context directory path.
KeyContext = "context"
// Build base context directory path.
KeyContextDirectory = "context"
// Dockerfile stage to build up to.
KeyTarget = "target"
// Key=value metadata labels to apply to the image.
Expand All @@ -73,6 +73,8 @@ const (
KeyOutput = "outputs"
// Unique build identifier.
KeyBuildID = "build-id"
// Additional Build contexts (--build-context).
KeyBuildContext = "build-context"
)

const (
Expand Down Expand Up @@ -103,6 +105,7 @@ type BOpts struct {
Outputs []string
Labels map[string]string
ProgressWriter progresswriter.Writer
BuildContexts map[string]string

ContentStore *content.ContentStoreProxy
Resolver *resolver.ResolverProxy
Expand Down Expand Up @@ -169,7 +172,7 @@ func NewBuildOpts(ctx context.Context, basePath string, contextMap map[string][]
}

ctxDir := "."
if c, ok := first(KeyContext); ok {
if c, ok := first(KeyContextDirectory); ok {
ctxDir = c
}

Expand Down Expand Up @@ -272,6 +275,7 @@ func NewBuildOpts(ctx context.Context, basePath string, contextMap map[string][]

labels := mapExtract(KeyLabels)
buildArgs := mapExtract(KeyBuildArgs)
buildContexts := mapExtract(KeyBuildContext)
secrets, err := mapExtractB64(KeySecrets)
if err != nil {
return nil, err
Expand Down Expand Up @@ -350,7 +354,7 @@ func NewBuildOpts(ctx context.Context, basePath string, contextMap map[string][]
}
}

fssyncProxy, err := fssync.NewFSSyncProxy(".", basePath, addedGlobs, dockerfileBytes, dockerignoreBytes)
fssyncProxy, err := fssync.NewFSSyncProxy(ctxDir, basePath, addedGlobs, dockerfileBytes, dockerignoreBytes, buildContexts)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -383,6 +387,7 @@ func NewBuildOpts(ctx context.Context, basePath string, contextMap map[string][]
CacheOut: cacheOut,
Outputs: outputs,
basePath: filepath.Join(basePath, buildID),
BuildContexts: buildContexts,
}

return bopts, nil
Expand Down
104 changes: 76 additions & 28 deletions pkg/build/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/containerd/platforms"
dref "github.com/distribution/reference"

"github.com/apple/container-builder-shim/pkg/build/utils"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/client/llb/sourceresolver"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
Expand All @@ -42,8 +43,6 @@ import (
"github.com/moby/buildkit/util/progress/progresswriter"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"

"github.com/apple/container-builder-shim/pkg/build/utils"
)

func frontend(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
Expand Down Expand Up @@ -131,6 +130,79 @@ func resolveStates(ctx context.Context, bopts *BOpts, platform ocispecs.Platform
stateLock := sync.Mutex{}

resolveSource := func(resolvedBaseStageName string, sourcePlatform ocispecs.Platform) error {

saveState := func(img []byte, fqdn string, ref string, storeName string, opts ...llb.OCILayoutOption) error {
opts = append(opts, llb.Platform(sourcePlatform))
opts = append(opts, llb.OCIStore("", storeName))

st := llb.OCILayout(fqdn, opts...)

st, err = st.WithImageConfig(img)
if err != nil {
return err
}

named, err := dref.ParseNormalizedNamed(ref)
if err != nil {
return fmt.Errorf("invalid context name %s %v", ref, err)
}
// pname constructs a platform-qualified image reference in the format buildkit requires for digest resolution
name := strings.TrimSuffix(dref.FamiliarString(named), ":latest")
pname := name + "::" + platforms.FormatAll(platforms.Normalize(sourcePlatform))

imgMetaMap := map[string][]byte{
exptypes.ExporterImageConfigKey: img,
}
imgMeta, err := json.Marshal(imgMetaMap)
if err != nil {
return err
}

stateLock.Lock()
states[pname] = stateMeta{
state: st.Platform(sourcePlatform),
imgMeta: imgMeta,
}
stateLock.Unlock()
return nil
}

// handle build context
if val, ok := bopts.BuildContexts[resolvedBaseStageName]; ok {
// oci-layout requires custom handling as namedContext cannot load from client correctly
if strings.SplitN(val, ":", 2)[0] != "oci-layout" {
return nil
}
// passing in "oci-layout" as they are so client side knows that it is oci from build-context
resolverOpts := sourceresolver.Opt{}
resolverOpts.ImageOpt = &sourceresolver.ResolveImageOpt{
Platform: &sourcePlatform,
ResolveMode: llb.ResolveModePreferLocal.String(),
}
resolverOpts.OCILayoutOpt = &sourceresolver.ResolveOCILayoutOpt{
Store: sourceresolver.ResolveImageConfigOptStore{
StoreID: resolvedBaseStageName,
SessionID: "",
},
}
_, digest, img, err := bopts.Resolver.ResolveImageConfig(ctx, val, resolverOpts)
if err != nil {
if err == reference.ErrObjectRequired {
return nil
}
return err
}

// not using the returning `ref` here as the `ref` will be the local path like /User/path/to/oci-layout
// However, when using resolvedBaseStageName directly (ex: deps),
// and if we don't add some dummy host like "docker.io/library/".
// we will get an error like following
// Error: unknown: "failed to solve: failed to load cache key: parse "dummy://deps@sha256:xxx": invalid port ":xxx" after host"
ref := resolvedBaseStageName
fqdn := "docker.io/library/" + ref + "@" + digest.String()
return saveState(img, fqdn, ref, resolvedBaseStageName, llb.WithCustomName("[context "+resolvedBaseStageName+"] OCI load from client"))
}

if strings.EqualFold(resolvedBaseStageName, "scratch") || strings.EqualFold(resolvedBaseStageName, "context") {
return nil
}
Expand All @@ -152,7 +224,7 @@ func resolveStates(ctx context.Context, bopts *BOpts, platform ocispecs.Platform
}
resolverOpts.OCILayoutOpt = &sourceresolver.ResolveOCILayoutOpt{
Store: sourceresolver.ResolveImageConfigOptStore{
StoreID: "container",
StoreID: KeyContentStoreName,
SessionID: "",
},
}
Expand All @@ -175,31 +247,7 @@ func resolveStates(ctx context.Context, bopts *BOpts, platform ocispecs.Platform
if _, ok := ref.(dref.Digested); !ok {
fqdn += "@" + digest.String()
}
st := llb.OCILayout(fqdn, llb.OCIStore("", "container"), llb.Platform(sourcePlatform))

named, err := dref.ParseNormalizedNamed(ref.String())
if err != nil {
return fmt.Errorf("invalid context name %s %v", ref.String(), err)
}
// pname constructs a platform-qualified image reference in the format buildkit requires for digest resolution
name := strings.TrimSuffix(dref.FamiliarString(named), ":latest")
pname := name + "::" + platforms.FormatAll(platforms.Normalize(sourcePlatform))

imgMetaMap := map[string][]byte{
exptypes.ExporterImageConfigKey: img,
}
imgMeta, err := json.Marshal(imgMetaMap)
if err != nil {
return err
}

stateLock.Lock()
states[pname] = stateMeta{
state: st.Platform(sourcePlatform),
imgMeta: imgMeta,
}
stateLock.Unlock()
return nil
return saveState(img, fqdn, ref.String(), KeyContentStoreName)
}

for i, stage := range stages {
Expand Down
5 changes: 4 additions & 1 deletion pkg/fssync/fssync.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ type FSSyncProxy struct {

dockerfile []byte
dockerignore []byte

buildContexts map[string]string
}

func NewFSSyncProxy(contextDir string, basePath string, addedGlobs []string,
dockerfile []byte, dockerignore []byte) (*FSSyncProxy, error) {
dockerfile []byte, dockerignore []byte, buildContexts map[string]string) (*FSSyncProxy, error) {

f := new(FSSyncProxy)
f.contextDir = contextDir
Expand All @@ -70,6 +72,7 @@ func NewFSSyncProxy(contextDir string, basePath string, addedGlobs []string,

f.dockerfile = dockerfile
f.dockerignore = dockerignore
f.buildContexts = buildContexts
return f, nil
}

Expand Down
8 changes: 7 additions & 1 deletion pkg/fssync/walk.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,17 @@ func (f *FS) Walk(ctx context.Context, target string, fn fs.WalkDirFunc) error {
if followPaths == "" {
followPaths = strings.Join(f.proxy.addedGlobs, ",")
}
root := f.root
if walkMeta.DirName != "" && walkMeta.DirName != "context" && walkMeta.DirName != "scratch" {
if val, ok := f.proxy.buildContexts[walkMeta.DirName]; ok {
root = val
}
}

packet := &api.BuildTransfer{
Id: id,
Direction: api.TransferDirection_OUTOF,
Source: &f.root,
Source: &root,
Metadata: map[string]string{
"os": "linux",
"stage": "fssync",
Expand Down