mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-03 17:43:42 +08:00 
			
		
		
		
	Merge pull request #1181 from crazy-max/compose-consistency
bake: fix compose consistency check
This commit is contained in:
		@@ -278,9 +278,19 @@ services:
 | 
			
		||||
`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fp3 := File{
 | 
			
		||||
		Name: "docker-compose3.yml",
 | 
			
		||||
		Data: []byte(
 | 
			
		||||
			`version: "3"
 | 
			
		||||
services:
 | 
			
		||||
  webapp:
 | 
			
		||||
    entrypoint: echo 1
 | 
			
		||||
`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
 | 
			
		||||
	m, g, err := ReadTargets(ctx, []File{fp, fp2}, []string{"default"}, nil, nil)
 | 
			
		||||
	m, g, err := ReadTargets(ctx, []File{fp, fp2, fp3}, []string{"default"}, nil, nil)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	require.Equal(t, 3, len(m))
 | 
			
		||||
@@ -446,6 +456,40 @@ func TestReadContextFromTargetUnknown(t *testing.T) {
 | 
			
		||||
	require.Error(t, err)
 | 
			
		||||
	require.Contains(t, err.Error(), "failed to find target bar")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestReadEmptyTargets(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	fp := File{
 | 
			
		||||
		Name: "docker-bake.hcl",
 | 
			
		||||
		Data: []byte(`target "app1" {}`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fp2 := File{
 | 
			
		||||
		Name: "docker-compose.yml",
 | 
			
		||||
		Data: []byte(`
 | 
			
		||||
services:
 | 
			
		||||
  app2: {}
 | 
			
		||||
`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
 | 
			
		||||
	m, _, err := ReadTargets(ctx, []File{fp, fp2}, []string{"app1", "app2"}, nil, nil)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	require.Equal(t, 2, len(m))
 | 
			
		||||
	_, ok := m["app1"]
 | 
			
		||||
	require.True(t, ok)
 | 
			
		||||
	_, ok = m["app2"]
 | 
			
		||||
	require.True(t, ok)
 | 
			
		||||
 | 
			
		||||
	require.Equal(t, "Dockerfile", *m["app1"].Dockerfile)
 | 
			
		||||
	require.Equal(t, ".", *m["app1"].Context)
 | 
			
		||||
	require.Equal(t, "Dockerfile", *m["app2"].Dockerfile)
 | 
			
		||||
	require.Equal(t, ".", *m["app2"].Context)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestReadContextFromTargetChain(t *testing.T) {
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
	fp := File{
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ package bake
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/compose-spec/compose-go/loader"
 | 
			
		||||
@@ -12,6 +11,9 @@ import (
 | 
			
		||||
	"gopkg.in/yaml.v3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// errComposeInvalid is returned when a compose file is invalid
 | 
			
		||||
var errComposeInvalid = errors.New("invalid compose file")
 | 
			
		||||
 | 
			
		||||
func parseCompose(dt []byte) (*compose.Project, error) {
 | 
			
		||||
	return loader.Load(compose.ConfigDetails{
 | 
			
		||||
		ConfigFiles: []compose.ConfigFile{
 | 
			
		||||
@@ -22,6 +24,7 @@ func parseCompose(dt []byte) (*compose.Project, error) {
 | 
			
		||||
		Environment: envMap(os.Environ()),
 | 
			
		||||
	}, func(options *loader.Options) {
 | 
			
		||||
		options.SkipNormalization = true
 | 
			
		||||
		options.SkipConsistencyCheck = true
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -42,9 +45,11 @@ func ParseCompose(dt []byte) (*Config, error) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err = composeValidate(cfg); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var c Config
 | 
			
		||||
	var zeroBuildConfig compose.BuildConfig
 | 
			
		||||
	if len(cfg.Services) > 0 {
 | 
			
		||||
		c.Groups = []*Group{}
 | 
			
		||||
		c.Targets = []*Target{}
 | 
			
		||||
@@ -52,13 +57,8 @@ func ParseCompose(dt []byte) (*Config, error) {
 | 
			
		||||
		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
 | 
			
		||||
			if s.Build == nil {
 | 
			
		||||
				s.Build = &compose.BuildConfig{}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err = validateTargetName(s.Name); err != nil {
 | 
			
		||||
@@ -219,6 +219,28 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// compposeValidate validates a compose file
 | 
			
		||||
func composeValidate(project *compose.Project) error {
 | 
			
		||||
	for _, s := range project.Services {
 | 
			
		||||
		if s.Build != nil {
 | 
			
		||||
			for _, secret := range s.Build.Secrets {
 | 
			
		||||
				if _, ok := project.Secrets[secret.Source]; !ok {
 | 
			
		||||
					return errors.Wrap(errComposeInvalid, fmt.Sprintf("service %q refers to undefined build secret %s", s.Name, secret.Source))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for name, secret := range project.Secrets {
 | 
			
		||||
		if secret.External.External {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if secret.File == "" && secret.Environment == "" {
 | 
			
		||||
			return errors.Wrap(errComposeInvalid, fmt.Sprintf("secret %q must declare either `file` or `environment`", name))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// composeToBuildkitSecret converts secret from compose format to buildkit's
 | 
			
		||||
// csv format.
 | 
			
		||||
func composeToBuildkitSecret(inp compose.ServiceSecretConfig, psecret compose.SecretConfig) (string, error) {
 | 
			
		||||
 
 | 
			
		||||
@@ -153,21 +153,15 @@ services:
 | 
			
		||||
	require.Equal(t, c.Targets[0].Args["BRB"], "FOO")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBogusCompose(t *testing.T) {
 | 
			
		||||
func TestInconsistentComposeFile(t *testing.T) {
 | 
			
		||||
	var dt = []byte(`
 | 
			
		||||
services:
 | 
			
		||||
  db:
 | 
			
		||||
    labels:
 | 
			
		||||
      - "foo"
 | 
			
		||||
  webapp:
 | 
			
		||||
    build:
 | 
			
		||||
      context: .
 | 
			
		||||
      target: webapp
 | 
			
		||||
    entrypoint: echo 1
 | 
			
		||||
`)
 | 
			
		||||
 | 
			
		||||
	_, err := ParseCompose(dt)
 | 
			
		||||
	require.Error(t, err)
 | 
			
		||||
	require.Contains(t, err.Error(), "has neither an image nor a build context specified: invalid compose project")
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAdvancedNetwork(t *testing.T) {
 | 
			
		||||
@@ -420,3 +414,69 @@ services:
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestValidateComposeSecret(t *testing.T) {
 | 
			
		||||
	cases := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		dt      []byte
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "secret set by file",
 | 
			
		||||
			dt: []byte(`
 | 
			
		||||
secrets:
 | 
			
		||||
  foo:
 | 
			
		||||
    file: .secret
 | 
			
		||||
`),
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "secret set by environment",
 | 
			
		||||
			dt: []byte(`
 | 
			
		||||
secrets:
 | 
			
		||||
  foo:
 | 
			
		||||
    environment: TOKEN
 | 
			
		||||
`),
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "external secret",
 | 
			
		||||
			dt: []byte(`
 | 
			
		||||
secrets:
 | 
			
		||||
  foo:
 | 
			
		||||
    external: true
 | 
			
		||||
`),
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "unset secret",
 | 
			
		||||
			dt: []byte(`
 | 
			
		||||
secrets:
 | 
			
		||||
  foo: {}
 | 
			
		||||
`),
 | 
			
		||||
			wantErr: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "undefined secret",
 | 
			
		||||
			dt: []byte(`
 | 
			
		||||
services:
 | 
			
		||||
  foo:
 | 
			
		||||
    build:
 | 
			
		||||
      secrets:
 | 
			
		||||
        - token
 | 
			
		||||
`),
 | 
			
		||||
			wantErr: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range cases {
 | 
			
		||||
		tt := tt
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			_, err := ParseCompose(tt.dt)
 | 
			
		||||
			if tt.wantErr {
 | 
			
		||||
				require.Error(t, err)
 | 
			
		||||
			} else {
 | 
			
		||||
				require.NoError(t, err)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user