mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 10:03:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			195 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package bake
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"reflect"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/compose-spec/compose-go/loader"
 | 
						|
	compose "github.com/compose-spec/compose-go/types"
 | 
						|
)
 | 
						|
 | 
						|
func parseCompose(dt []byte) (*compose.Project, error) {
 | 
						|
	return loader.Load(compose.ConfigDetails{
 | 
						|
		ConfigFiles: []compose.ConfigFile{
 | 
						|
			{
 | 
						|
				Content: dt,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Environment: envMap(os.Environ()),
 | 
						|
	}, func(options *loader.Options) {
 | 
						|
		options.SkipNormalization = true
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func envMap(env []string) map[string]string {
 | 
						|
	result := make(map[string]string, len(env))
 | 
						|
	for _, s := range env {
 | 
						|
		kv := strings.SplitN(s, "=", 2)
 | 
						|
		if len(kv) != 2 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		result[kv[0]] = kv[1]
 | 
						|
	}
 | 
						|
	return result
 | 
						|
}
 | 
						|
 | 
						|
func ParseCompose(dt []byte) (*Config, error) {
 | 
						|
	cfg, err := parseCompose(dt)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	var c Config
 | 
						|
	var zeroBuildConfig compose.BuildConfig
 | 
						|
	if len(cfg.Services) > 0 {
 | 
						|
		c.Groups = []*Group{}
 | 
						|
		c.Targets = []*Target{}
 | 
						|
 | 
						|
		g := &Group{Name: "default"}
 | 
						|
 | 
						|
		for _, s := range cfg.Services {
 | 
						|
 | 
						|
			if s.Build == nil || reflect.DeepEqual(s.Build, zeroBuildConfig) {
 | 
						|
				// if not make sure they're setting an image or it's invalid d-c.yml
 | 
						|
				if s.Image == "" {
 | 
						|
					return nil, fmt.Errorf("compose file invalid: service %s has neither an image nor a build context specified. At least one must be provided", s.Name)
 | 
						|
				}
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			var contextPathP *string
 | 
						|
			if s.Build.Context != "" {
 | 
						|
				contextPath := s.Build.Context
 | 
						|
				contextPathP = &contextPath
 | 
						|
			}
 | 
						|
			var dockerfilePathP *string
 | 
						|
			if s.Build.Dockerfile != "" {
 | 
						|
				dockerfilePath := s.Build.Dockerfile
 | 
						|
				dockerfilePathP = &dockerfilePath
 | 
						|
			}
 | 
						|
			g.Targets = append(g.Targets, s.Name)
 | 
						|
			t := &Target{
 | 
						|
				Name:       s.Name,
 | 
						|
				Context:    contextPathP,
 | 
						|
				Dockerfile: dockerfilePathP,
 | 
						|
				Labels:     s.Build.Labels,
 | 
						|
				Args: flatten(s.Build.Args.Resolve(func(val string) (string, bool) {
 | 
						|
					val, ok := cfg.Environment[val]
 | 
						|
					return val, ok
 | 
						|
				})),
 | 
						|
				CacheFrom: s.Build.CacheFrom,
 | 
						|
			}
 | 
						|
			if err = t.composeExtTarget(s.Build.Extensions); err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			if s.Build.Target != "" {
 | 
						|
				target := s.Build.Target
 | 
						|
				t.Target = &target
 | 
						|
			}
 | 
						|
			if len(t.Tags) == 0 && s.Image != "" {
 | 
						|
				t.Tags = []string{s.Image}
 | 
						|
			}
 | 
						|
			c.Targets = append(c.Targets, t)
 | 
						|
		}
 | 
						|
		c.Groups = append(c.Groups, g)
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
	return &c, nil
 | 
						|
}
 | 
						|
 | 
						|
func flatten(in compose.MappingWithEquals) compose.Mapping {
 | 
						|
	if len(in) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	out := compose.Mapping{}
 | 
						|
	for k, v := range in {
 | 
						|
		if v == nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		out[k] = *v
 | 
						|
	}
 | 
						|
	return out
 | 
						|
}
 | 
						|
 | 
						|
// composeExtTarget converts Compose build extension x-bake to bake Target
 | 
						|
// https://github.com/compose-spec/compose-spec/blob/master/spec.md#extension
 | 
						|
func (t *Target) composeExtTarget(exts map[string]interface{}) error {
 | 
						|
	if ext, ok := exts["x-bake"]; ok {
 | 
						|
		for key, val := range ext.(map[string]interface{}) {
 | 
						|
			switch key {
 | 
						|
			case "tags":
 | 
						|
				if res, k := val.(string); k {
 | 
						|
					t.Tags = append(t.Tags, res)
 | 
						|
				} else {
 | 
						|
					for _, res := range val.([]interface{}) {
 | 
						|
						t.Tags = append(t.Tags, res.(string))
 | 
						|
					}
 | 
						|
				}
 | 
						|
			case "cache-from":
 | 
						|
				t.CacheFrom = []string{} // Needed to override the main field
 | 
						|
				if res, k := val.(string); k {
 | 
						|
					t.CacheFrom = append(t.CacheFrom, res)
 | 
						|
				} else {
 | 
						|
					for _, res := range val.([]interface{}) {
 | 
						|
						t.CacheFrom = append(t.CacheFrom, res.(string))
 | 
						|
					}
 | 
						|
				}
 | 
						|
			case "cache-to":
 | 
						|
				if res, k := val.(string); k {
 | 
						|
					t.CacheTo = append(t.CacheTo, res)
 | 
						|
				} else {
 | 
						|
					for _, res := range val.([]interface{}) {
 | 
						|
						t.CacheTo = append(t.CacheTo, res.(string))
 | 
						|
					}
 | 
						|
				}
 | 
						|
			case "secret":
 | 
						|
				if res, k := val.(string); k {
 | 
						|
					t.Secrets = append(t.Secrets, res)
 | 
						|
				} else {
 | 
						|
					for _, res := range val.([]interface{}) {
 | 
						|
						t.Secrets = append(t.Secrets, res.(string))
 | 
						|
					}
 | 
						|
				}
 | 
						|
			case "ssh":
 | 
						|
				if res, k := val.(string); k {
 | 
						|
					t.SSH = append(t.SSH, res)
 | 
						|
				} else {
 | 
						|
					for _, res := range val.([]interface{}) {
 | 
						|
						t.SSH = append(t.SSH, res.(string))
 | 
						|
					}
 | 
						|
				}
 | 
						|
			case "platforms":
 | 
						|
				if res, k := val.(string); k {
 | 
						|
					t.Platforms = append(t.Platforms, res)
 | 
						|
				} else {
 | 
						|
					for _, res := range val.([]interface{}) {
 | 
						|
						t.Platforms = append(t.Platforms, res.(string))
 | 
						|
					}
 | 
						|
				}
 | 
						|
			case "output":
 | 
						|
				if res, k := val.(string); k {
 | 
						|
					t.Outputs = append(t.Outputs, res)
 | 
						|
				} else {
 | 
						|
					for _, res := range val.([]interface{}) {
 | 
						|
						t.Outputs = append(t.Outputs, res.(string))
 | 
						|
					}
 | 
						|
				}
 | 
						|
			case "pull":
 | 
						|
				if res, ok := val.(bool); ok {
 | 
						|
					t.Pull = &res
 | 
						|
				}
 | 
						|
			case "no-cache":
 | 
						|
				if res, ok := val.(bool); ok {
 | 
						|
					t.NoCache = &res
 | 
						|
				}
 | 
						|
			default:
 | 
						|
				return fmt.Errorf("compose file invalid: unkwown %s field for x-bake", key)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 |