Skip to content

Commit

Permalink
feat: build image by platform (#1582)
Browse files Browse the repository at this point in the history
* feat: build image by platform

Signed-off-by: hang lv <[email protected]>

* feat: support sequential multi-platform image building

Signed-off-by: hang lv <[email protected]>

* feat: set goos/goarch as default target platform

Signed-off-by: hang lv <[email protected]>

* fix: lint

Signed-off-by: hang lv <[email protected]>

* fix: pull correct base image

Signed-off-by: hang lv <[email protected]>

---------

Signed-off-by: hang lv <[email protected]>
  • Loading branch information
n063h authored May 12, 2023
1 parent 2839aa3 commit 6569021
Show file tree
Hide file tree
Showing 18 changed files with 135 additions and 33 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ require (
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect
Expand Down
34 changes: 27 additions & 7 deletions pkg/app/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package app

import (
"strings"
"time"

"github.com/sirupsen/logrus"
Expand All @@ -23,6 +24,7 @@ import (
buildutil "github.com/tensorchord/envd/pkg/app/build"
"github.com/tensorchord/envd/pkg/app/telemetry"
sshconfig "github.com/tensorchord/envd/pkg/ssh/config"
"github.com/tensorchord/envd/pkg/util/runtimeutil"
)

var CommandBuild = &cli.Command{
Expand Down Expand Up @@ -89,6 +91,12 @@ To build and push the image to a registry:
Usage: "Import the cache (e.g. type=registry,ref=<image>)",
Aliases: []string{"ic"},
},
&cli.StringFlag{
Name: "platform",
Usage: `Specify the target platforms for the build output (for example, windows/amd64 or linux/amd64,darwin/arm64).
Build images with same tags could cause image overwriting, platform suffixes will be added to differentiate the images.`,
DefaultText: runtimeutil.GetRuntimePlatform(),
},
},
Action: build,
}
Expand All @@ -106,12 +114,24 @@ func build(clicontext *cli.Context) error {
logger := logrus.WithField("builder-options", opt)
logger.Debug("starting build command")

builder, err := buildutil.GetBuilder(clicontext, opt)
if err != nil {
return err
}
if err = buildutil.InterpretEnvdDef(builder); err != nil {
return err
platforms := strings.Split(opt.Platform, ",")
for _, platform := range platforms {
o := opt
o.Platform = platform
if len(platforms) > 1 {
// Transform the platform suffix to comply with the tag naming rule.
o.Tag += "-" + strings.Replace(platform, "/", "-", 1)
}
builder, err := buildutil.GetBuilder(clicontext, o)
if err != nil {
return err
}
if err = buildutil.InterpretEnvdDef(builder); err != nil {
return err
}
if err := buildutil.BuildImage(clicontext, builder); err != nil {
return err
}
}
return buildutil.BuildImage(clicontext, builder)
return nil
}
2 changes: 2 additions & 0 deletions pkg/app/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ func ParseBuildOpt(clicontext *cli.Context) (builder.Options, error) {
exportCache := clicontext.String("export-cache")
importCache := clicontext.String("import-cache")
useProxy := clicontext.Bool("use-proxy")
platform := clicontext.String("platform")

opt := builder.Options{
ManifestFilePath: manifest,
Expand All @@ -148,6 +149,7 @@ func ParseBuildOpt(clicontext *cli.Context) (builder.Options, error) {
ExportCache: exportCache,
ImportCache: importCache,
UseHTTPProxy: useProxy,
Platform: platform,
}

debug := clicontext.Bool("debug")
Expand Down
6 changes: 6 additions & 0 deletions pkg/app/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/tensorchord/envd/pkg/home"
sshconfig "github.com/tensorchord/envd/pkg/ssh/config"
"github.com/tensorchord/envd/pkg/types"
"github.com/tensorchord/envd/pkg/util/runtimeutil"
)

var CommandUp = &cli.Command{
Expand Down Expand Up @@ -141,6 +142,11 @@ var CommandUp = &cli.Command{
Usage: "Import the cache (e.g. type=registry,ref=<image>)",
Aliases: []string{"ic"},
},
&cli.StringFlag{
Name: "platform",
Usage: "Specify the target platform for the build output, (for example, windows/amd64, linux/amd64, or darwin/arm64)",
DefaultText: runtimeutil.GetRuntimePlatform(),
},
},

Action: up,
Expand Down
27 changes: 25 additions & 2 deletions pkg/builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import (
"io"
"os"
"path/filepath"
"strings"

"github.com/cockroachdb/errors"
"github.com/docker/cli/cli/config"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/auth/authprovider"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"

Expand Down Expand Up @@ -157,7 +159,11 @@ func (b generalBuilder) Interpret() error {

func (b generalBuilder) Compile(ctx context.Context) (*llb.Definition, error) {
envName := filepath.Base(b.BuildContextDir)
def, err := b.graph.Compile(ctx, envName, b.PubKeyPath, b.Options.ProgressMode)
platform, err := parsePlatform(b.Platform)
if err != nil {
return nil, err
}
def, err := b.graph.Compile(ctx, envName, b.PubKeyPath, platform, b.Options.ProgressMode)
if err != nil {
return nil, errors.Wrap(err, "failed to compile build.envd")
}
Expand Down Expand Up @@ -191,8 +197,9 @@ func (b generalBuilder) imageConfig(ctx context.Context) (string, error) {

env := b.graph.GetEnviron()
user := b.graph.GetUser()
platform := b.graph.GetPlatform()

data, err := ImageConfigStr(labels, ports, ep, env, user)
data, err := ImageConfigStr(labels, ports, ep, env, user, platform)
if err != nil {
return "", errors.Wrap(err, "failed to get image config")
}
Expand Down Expand Up @@ -335,3 +342,19 @@ func constructSolveOpt(ce []client.CacheOptionsEntry, entry client.ExportEntry,
}
return opt
}

func parsePlatform(platform string) (*ocispecs.Platform, error) {
os, arch, variant := "linux", "amd64", ""
if platform == "" {
return &ocispecs.Platform{Architecture: arch, OS: os, Variant: variant}, nil
}
arr := strings.Split(platform, "/")
if len(arr) < 2 {
return nil, errors.New("invalid platform format, expected `os/arch[/variant]`")
}
os, arch = arr[0], arr[1]
if len(arr) >= 3 {
variant = arr[2]
}
return &ocispecs.Platform{Architecture: arch, OS: os, Variant: variant}, nil
}
3 changes: 3 additions & 0 deletions pkg/builder/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ type Options struct {
ImportCache string
// UseHTTPProxy uses HTTPS_PROXY/HTTP_PROXY/NO_PROXY in the build process.
UseHTTPProxy bool
// Specify the target platform for the build output.
// e.g. platform=linux/arm64,linux/amd64
Platform string
}

type generalBuilder struct {
Expand Down
14 changes: 7 additions & 7 deletions pkg/builder/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
"github.com/containerd/console"
"github.com/moby/buildkit/client"
gatewayclient "github.com/moby/buildkit/frontend/gateway/client"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
)

Expand All @@ -35,20 +35,20 @@ const (
)

func ImageConfigStr(labels map[string]string, ports map[string]struct{},
entrypoint []string, env []string, user string) (string, error) {
img := v1.Image{
Config: v1.ImageConfig{
entrypoint []string, env []string, user string, platform *ocispecs.Platform) (string, error) {
img := ocispecs.Image{
Config: ocispecs.ImageConfig{
Labels: labels,
User: user,
WorkingDir: "/",
Env: env,
ExposedPorts: ports,
Entrypoint: entrypoint,
},
Architecture: "amd64",
Architecture: platform.Architecture,
// Refer to https://github.com/tensorchord/envd/issues/269#issuecomment-1152944914
OS: "linux",
RootFS: v1.RootFS{
OS: platform.OS,
RootFS: ocispecs.RootFS{
Type: "layers",
},
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/editor/vscode/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"strings"

"github.com/cockroachdb/errors"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
)

Expand All @@ -38,7 +38,7 @@ const (
PLATFORM_ALPINE_X64 = "alpine-x64"
)

func ConvertLLBPlatform(platform *v1.Platform) (string, error) {
func ConvertLLBPlatform(platform *ocispecs.Platform) (string, error) {
// Convert opencontainers style platform to VSCode extension style platform.
switch platform.OS {
case "windows":
Expand Down
4 changes: 3 additions & 1 deletion pkg/lang/ir/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import (
"context"

"github.com/moby/buildkit/client/llb"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"

"github.com/tensorchord/envd/pkg/progress/compileui"
)

type Graph interface {
Compile(ctx context.Context, envName string, pub string, progressMode string) (*llb.Definition, error)
Compile(ctx context.Context, envName string, pub string, platform *ocispecs.Platform, progressMode string) (*llb.Definition, error)

graphDebugger
graphVisitor
Expand Down Expand Up @@ -56,4 +57,5 @@ type graphVisitor interface {
GetHTTP() []HTTPInfo
GetRuntimeCommands() map[string]string
GetUser() string
GetPlatform() *ocispecs.Platform
}
6 changes: 5 additions & 1 deletion pkg/lang/ir/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@ import (
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)

func FetchImageConfig(ctx context.Context, imageName string) (config v1.ImageConfig, err error) {
func FetchImageConfig(ctx context.Context, imageName string, platform *v1.Platform) (config v1.ImageConfig, err error) {
ref, err := docker.ParseReference(fmt.Sprintf("//%s", imageName))
if err != nil {
return config, errors.Wrap(err, "failed to parse image reference")
}
sys := types.SystemContext{}
if platform != nil {
sys.ArchitectureChoice = platform.Architecture
sys.OSChoice = platform.OS
}
src, err := ref.NewImageSource(ctx, &sys)
if err != nil {
return config, errors.Wrap(err, "failed to get image source from ref")
Expand Down
12 changes: 9 additions & 3 deletions pkg/lang/ir/v0/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/cockroachdb/errors"
"github.com/moby/buildkit/client/llb"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
servertypes "github.com/tensorchord/envd-server/api/types"
Expand Down Expand Up @@ -65,6 +66,7 @@ func NewGraph() ir.Graph {
Shell: shellBASH,
CondaConfig: conda,
RuntimeGraph: runtimeGraph,
Platform: &ocispecs.Platform{},
}
}

Expand Down Expand Up @@ -106,14 +108,19 @@ func (g generalGraph) GetUser() string {
return "envd"
}

func (g *generalGraph) Compile(ctx context.Context, envName string, pub string, progressMode string) (*llb.Definition, error) {
func (g generalGraph) GetPlatform() *ocispecs.Platform {
return g.Platform
}

func (g *generalGraph) Compile(ctx context.Context, envName string, pub string, platform *ocispecs.Platform, progressMode string) (*llb.Definition, error) {
w, err := compileui.New(ctx, os.Stdout, progressMode)
if err != nil {
return nil, errors.Wrap(err, "failed to create compileui")
}
g.Writer = w
g.EnvironmentName = envName
g.PublicKeyPath = pub
g.Platform = platform

uid, gid, err := getUIDGID()
if err != nil {
Expand All @@ -123,8 +130,7 @@ func (g *generalGraph) Compile(ctx context.Context, envName string, pub string,
if err != nil {
return nil, errors.Wrap(err, "failed to compile the graph")
}
// TODO(gaocegege): Support multi platform.
def, err := state.Marshal(ctx, llb.LinuxAmd64)
def, err := state.Marshal(ctx, llb.Platform(*g.Platform))
if err != nil {
return nil, errors.Wrap(err, "failed to marshal the llb definition")
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/lang/ir/v0/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package v0

import (
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"

"github.com/tensorchord/envd/pkg/editor/vscode"
"github.com/tensorchord/envd/pkg/lang/ir"
"github.com/tensorchord/envd/pkg/progress/compileui"
Expand Down Expand Up @@ -77,6 +79,8 @@ type generalGraph struct {
EnvironmentName string

ir.RuntimeGraph

Platform *ocispecs.Platform
}

const (
Expand Down
12 changes: 9 additions & 3 deletions pkg/lang/ir/v1/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/cockroachdb/errors"
"github.com/moby/buildkit/client/llb"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
servertypes "github.com/tensorchord/envd-server/api/types"

Expand Down Expand Up @@ -57,6 +58,7 @@ func NewGraph() ir.Graph {
UserDirectories: []string{},
Shell: shellBASH,
RuntimeGraph: runtimeGraph,
Platform: &ocispecs.Platform{},
}
}

Expand Down Expand Up @@ -102,14 +104,19 @@ func (g generalGraph) GetRuntimeCommands() map[string]string {
return g.RuntimeCommands
}

func (g *generalGraph) Compile(ctx context.Context, envName string, pub string, progressMode string) (*llb.Definition, error) {
func (g generalGraph) GetPlatform() *ocispecs.Platform {
return g.Platform
}

func (g *generalGraph) Compile(ctx context.Context, envName string, pub string, platform *ocispecs.Platform, progressMode string) (*llb.Definition, error) {
w, err := compileui.New(ctx, os.Stdout, progressMode)
if err != nil {
return nil, errors.Wrap(err, "failed to create compileui")
}
g.Writer = w
g.EnvironmentName = envName
g.PublicKeyPath = pub
g.Platform = platform

uid, gid, err := g.getUIDGID()
if err != nil {
Expand All @@ -119,8 +126,7 @@ func (g *generalGraph) Compile(ctx context.Context, envName string, pub string,
if err != nil {
return nil, errors.Wrap(err, "failed to compile the graph")
}
// TODO(gaocegege): Support multi platform.
def, err := state.Marshal(ctx, llb.LinuxAmd64)
def, err := state.Marshal(ctx, llb.Platform(*g.Platform))
if err != nil {
return nil, errors.Wrap(err, "failed to marshal the llb definition")
}
Expand Down
5 changes: 1 addition & 4 deletions pkg/lang/ir/v1/editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (

"github.com/cockroachdb/errors"
"github.com/moby/buildkit/client/llb"
v1 "github.com/opencontainers/image-spec/specs-go/v1"

"github.com/tensorchord/envd/pkg/config"
"github.com/tensorchord/envd/pkg/editor/vscode"
Expand All @@ -31,9 +30,7 @@ import (
)

func (g generalGraph) compileVSCode(root llb.State) (llb.State, error) {
// TODO(n063h): support multiple platforms
p := &v1.Platform{Architecture: "amd64", OS: "linux"}
platform, err := vscode.ConvertLLBPlatform(p)
platform, err := vscode.ConvertLLBPlatform(g.Platform)
if err != nil {
return llb.State{}, errors.Wrap(err, "failed to convert llb platform")
}
Expand Down
Loading

0 comments on commit 6569021

Please sign in to comment.