github.com/docker/buildx@v0.14.1-0.20240514123050-afcb609966dc/bake/bake_test.go (about)

     1  package bake
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path/filepath"
     7  	"sort"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func TestReadTargets(t *testing.T) {
    16  	fp := File{
    17  		Name: "config.hcl",
    18  		Data: []byte(`
    19  target "webDEP" {
    20  	args = {
    21  		VAR_INHERITED = "webDEP"
    22  		VAR_BOTH = "webDEP"
    23  	}
    24  	no-cache = true
    25  	shm-size = "128m"
    26  	ulimits = ["nofile=1024:1024"]
    27  }
    28  
    29  target "webapp" {
    30  	dockerfile = "Dockerfile.webapp"
    31  	args = {
    32  		VAR_BOTH = "webapp"
    33  	}
    34  	inherits = ["webDEP"]
    35  }`),
    36  	}
    37  
    38  	ctx := context.TODO()
    39  
    40  	t.Run("NoOverrides", func(t *testing.T) {
    41  		t.Parallel()
    42  		m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, nil, nil)
    43  		require.NoError(t, err)
    44  		require.Equal(t, 1, len(m))
    45  
    46  		require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
    47  		require.Equal(t, ".", *m["webapp"].Context)
    48  		require.Equal(t, ptrstr("webDEP"), m["webapp"].Args["VAR_INHERITED"])
    49  		require.Equal(t, true, *m["webapp"].NoCache)
    50  		require.Equal(t, "128m", *m["webapp"].ShmSize)
    51  		require.Equal(t, []string{"nofile=1024:1024"}, m["webapp"].Ulimits)
    52  		require.Nil(t, m["webapp"].Pull)
    53  
    54  		require.Equal(t, 1, len(g))
    55  		require.Equal(t, []string{"webapp"}, g["default"].Targets)
    56  	})
    57  
    58  	t.Run("InvalidTargetOverrides", func(t *testing.T) {
    59  		t.Parallel()
    60  		_, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"nosuchtarget.context=foo"}, nil)
    61  		require.NotNil(t, err)
    62  		require.Equal(t, err.Error(), "could not find any target matching 'nosuchtarget'")
    63  	})
    64  
    65  	t.Run("ArgsOverrides", func(t *testing.T) {
    66  		t.Run("leaf", func(t *testing.T) {
    67  			t.Setenv("VAR_FROMENV"+t.Name(), "fromEnv")
    68  
    69  			m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{
    70  				"webapp.args.VAR_UNSET",
    71  				"webapp.args.VAR_EMPTY=",
    72  				"webapp.args.VAR_SET=bananas",
    73  				"webapp.args.VAR_FROMENV" + t.Name(),
    74  				"webapp.args.VAR_INHERITED=override",
    75  				// not overriding VAR_BOTH on purpose
    76  			}, nil)
    77  			require.NoError(t, err)
    78  
    79  			require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
    80  			require.Equal(t, ".", *m["webapp"].Context)
    81  
    82  			_, isSet := m["webapp"].Args["VAR_UNSET"]
    83  			require.False(t, isSet, m["webapp"].Args["VAR_UNSET"])
    84  
    85  			_, isSet = m["webapp"].Args["VAR_EMPTY"]
    86  			require.True(t, isSet, m["webapp"].Args["VAR_EMPTY"])
    87  
    88  			require.Equal(t, ptrstr("bananas"), m["webapp"].Args["VAR_SET"])
    89  
    90  			require.Equal(t, ptrstr("fromEnv"), m["webapp"].Args["VAR_FROMENV"+t.Name()])
    91  
    92  			require.Equal(t, ptrstr("webapp"), m["webapp"].Args["VAR_BOTH"])
    93  			require.Equal(t, ptrstr("override"), m["webapp"].Args["VAR_INHERITED"])
    94  
    95  			require.Equal(t, 1, len(g))
    96  			require.Equal(t, []string{"webapp"}, g["default"].Targets)
    97  		})
    98  
    99  		// building leaf but overriding parent fields
   100  		t.Run("parent", func(t *testing.T) {
   101  			t.Parallel()
   102  			m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{
   103  				"webDEP.args.VAR_INHERITED=override",
   104  				"webDEP.args.VAR_BOTH=override",
   105  			}, nil)
   106  
   107  			require.NoError(t, err)
   108  			require.Equal(t, ptrstr("override"), m["webapp"].Args["VAR_INHERITED"])
   109  			require.Equal(t, ptrstr("webapp"), m["webapp"].Args["VAR_BOTH"])
   110  			require.Equal(t, 1, len(g))
   111  			require.Equal(t, []string{"webapp"}, g["default"].Targets)
   112  		})
   113  	})
   114  
   115  	t.Run("ContextOverride", func(t *testing.T) {
   116  		t.Parallel()
   117  		_, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context"}, nil)
   118  		require.NotNil(t, err)
   119  
   120  		m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context=foo"}, nil)
   121  		require.NoError(t, err)
   122  		require.Equal(t, "foo", *m["webapp"].Context)
   123  		require.Equal(t, 1, len(g))
   124  		require.Equal(t, []string{"webapp"}, g["default"].Targets)
   125  	})
   126  
   127  	t.Run("NoCacheOverride", func(t *testing.T) {
   128  		t.Parallel()
   129  		m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.no-cache=false"}, nil)
   130  		require.NoError(t, err)
   131  		require.Equal(t, false, *m["webapp"].NoCache)
   132  		require.Equal(t, 1, len(g))
   133  		require.Equal(t, []string{"webapp"}, g["default"].Targets)
   134  	})
   135  
   136  	t.Run("ShmSizeOverride", func(t *testing.T) {
   137  		m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.shm-size=256m"}, nil)
   138  		require.NoError(t, err)
   139  		require.Equal(t, "256m", *m["webapp"].ShmSize)
   140  	})
   141  
   142  	t.Run("PullOverride", func(t *testing.T) {
   143  		t.Parallel()
   144  		m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.pull=false"}, nil)
   145  		require.NoError(t, err)
   146  		require.Equal(t, false, *m["webapp"].Pull)
   147  		require.Equal(t, 1, len(g))
   148  		require.Equal(t, []string{"webapp"}, g["default"].Targets)
   149  	})
   150  
   151  	t.Run("PatternOverride", func(t *testing.T) {
   152  		t.Parallel()
   153  		// same check for two cases
   154  		multiTargetCheck := func(t *testing.T, m map[string]*Target, g map[string]*Group, err error) {
   155  			require.NoError(t, err)
   156  			require.Equal(t, 2, len(m))
   157  			require.Equal(t, "foo", *m["webapp"].Dockerfile)
   158  			require.Equal(t, ptrstr("webDEP"), m["webapp"].Args["VAR_INHERITED"])
   159  			require.Equal(t, "foo", *m["webDEP"].Dockerfile)
   160  			require.Equal(t, ptrstr("webDEP"), m["webDEP"].Args["VAR_INHERITED"])
   161  			require.Equal(t, 1, len(g))
   162  			sort.Strings(g["default"].Targets)
   163  			require.Equal(t, []string{"webDEP", "webapp"}, g["default"].Targets)
   164  		}
   165  
   166  		cases := []struct {
   167  			name      string
   168  			targets   []string
   169  			overrides []string
   170  			check     func(*testing.T, map[string]*Target, map[string]*Group, error)
   171  		}{
   172  			{
   173  				name:      "multi target single pattern",
   174  				targets:   []string{"webapp", "webDEP"},
   175  				overrides: []string{"web*.dockerfile=foo"},
   176  				check:     multiTargetCheck,
   177  			},
   178  			{
   179  				name:      "multi target multi pattern",
   180  				targets:   []string{"webapp", "webDEP"},
   181  				overrides: []string{"web*.dockerfile=foo", "*.args.VAR_BOTH=bar"},
   182  				check:     multiTargetCheck,
   183  			},
   184  			{
   185  				name:      "single target",
   186  				targets:   []string{"webapp"},
   187  				overrides: []string{"web*.dockerfile=foo"},
   188  				check: func(t *testing.T, m map[string]*Target, g map[string]*Group, err error) {
   189  					require.NoError(t, err)
   190  					require.Equal(t, 1, len(m))
   191  					require.Equal(t, "foo", *m["webapp"].Dockerfile)
   192  					require.Equal(t, ptrstr("webDEP"), m["webapp"].Args["VAR_INHERITED"])
   193  					require.Equal(t, 1, len(g))
   194  					require.Equal(t, []string{"webapp"}, g["default"].Targets)
   195  				},
   196  			},
   197  			{
   198  				name:      "nomatch",
   199  				targets:   []string{"webapp"},
   200  				overrides: []string{"nomatch*.dockerfile=foo"},
   201  				check: func(t *testing.T, m map[string]*Target, g map[string]*Group, err error) {
   202  					// NOTE: I am unsure whether failing to match should always error out
   203  					// instead of simply skipping that override.
   204  					// Let's enforce the error and we can relax it later if users complain.
   205  					require.NotNil(t, err)
   206  					require.Equal(t, err.Error(), "could not find any target matching 'nomatch*'")
   207  				},
   208  			},
   209  		}
   210  		for _, test := range cases {
   211  			t.Run(test.name, func(t *testing.T) {
   212  				m, g, err := ReadTargets(ctx, []File{fp}, test.targets, test.overrides, nil)
   213  				test.check(t, m, g, err)
   214  			})
   215  		}
   216  	})
   217  }
   218  
   219  func TestPushOverride(t *testing.T) {
   220  	t.Run("empty output", func(t *testing.T) {
   221  		fp := File{
   222  			Name: "docker-bake.hcl",
   223  			Data: []byte(
   224  				`target "app" {
   225  			}`),
   226  		}
   227  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=true"}, nil)
   228  		require.NoError(t, err)
   229  		require.Equal(t, 1, len(m["app"].Outputs))
   230  		require.Equal(t, "type=image,push=true", m["app"].Outputs[0])
   231  	})
   232  
   233  	t.Run("type image", func(t *testing.T) {
   234  		fp := File{
   235  			Name: "docker-bake.hcl",
   236  			Data: []byte(
   237  				`target "app" {
   238  				output = ["type=image,compression=zstd"]
   239  			}`),
   240  		}
   241  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=true"}, nil)
   242  		require.NoError(t, err)
   243  		require.Equal(t, 1, len(m["app"].Outputs))
   244  		require.Equal(t, "type=image,compression=zstd,push=true", m["app"].Outputs[0])
   245  	})
   246  
   247  	t.Run("type image push false", func(t *testing.T) {
   248  		fp := File{
   249  			Name: "docker-bake.hcl",
   250  			Data: []byte(
   251  				`target "app" {
   252  				output = ["type=image,compression=zstd"]
   253  			}`),
   254  		}
   255  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=false"}, nil)
   256  		require.NoError(t, err)
   257  		require.Equal(t, 1, len(m["app"].Outputs))
   258  		require.Equal(t, "type=image,compression=zstd,push=false", m["app"].Outputs[0])
   259  	})
   260  
   261  	t.Run("type registry", func(t *testing.T) {
   262  		fp := File{
   263  			Name: "docker-bake.hcl",
   264  			Data: []byte(
   265  				`target "app" {
   266  				output = ["type=registry"]
   267  			}`),
   268  		}
   269  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=true"}, nil)
   270  		require.NoError(t, err)
   271  		require.Equal(t, 1, len(m["app"].Outputs))
   272  		require.Equal(t, "type=registry", m["app"].Outputs[0])
   273  	})
   274  
   275  	t.Run("type registry push false", func(t *testing.T) {
   276  		fp := File{
   277  			Name: "docker-bake.hcl",
   278  			Data: []byte(
   279  				`target "app" {
   280  				output = ["type=registry"]
   281  			}`),
   282  		}
   283  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=false"}, nil)
   284  		require.NoError(t, err)
   285  		require.Equal(t, 0, len(m["app"].Outputs))
   286  	})
   287  
   288  	t.Run("type local and empty target", func(t *testing.T) {
   289  		fp := File{
   290  			Name: "docker-bake.hcl",
   291  			Data: []byte(
   292  				`target "foo" {
   293  		  		output = [ "type=local,dest=out" ]
   294  			}
   295  			target "bar" {
   296  			}`),
   297  		}
   298  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo", "bar"}, []string{"*.push=true"}, nil)
   299  		require.NoError(t, err)
   300  		require.Equal(t, 2, len(m))
   301  		require.Equal(t, 1, len(m["foo"].Outputs))
   302  		require.Equal(t, []string{"type=local,dest=out"}, m["foo"].Outputs)
   303  		require.Equal(t, 1, len(m["bar"].Outputs))
   304  		require.Equal(t, []string{"type=image,push=true"}, m["bar"].Outputs)
   305  	})
   306  }
   307  
   308  func TestLoadOverride(t *testing.T) {
   309  	t.Run("empty output", func(t *testing.T) {
   310  		fp := File{
   311  			Name: "docker-bake.hcl",
   312  			Data: []byte(
   313  				`target "app" {
   314  			}`),
   315  		}
   316  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
   317  		require.NoError(t, err)
   318  		require.Equal(t, 1, len(m["app"].Outputs))
   319  		require.Equal(t, "type=docker", m["app"].Outputs[0])
   320  	})
   321  
   322  	t.Run("type docker", func(t *testing.T) {
   323  		fp := File{
   324  			Name: "docker-bake.hcl",
   325  			Data: []byte(
   326  				`target "app" {
   327  				output = ["type=docker"]
   328  			}`),
   329  		}
   330  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
   331  		require.NoError(t, err)
   332  		require.Equal(t, 1, len(m["app"].Outputs))
   333  		require.Equal(t, []string{"type=docker"}, m["app"].Outputs)
   334  	})
   335  
   336  	t.Run("type image", func(t *testing.T) {
   337  		fp := File{
   338  			Name: "docker-bake.hcl",
   339  			Data: []byte(
   340  				`target "app" {
   341  				output = ["type=image"]
   342  			}`),
   343  		}
   344  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
   345  		require.NoError(t, err)
   346  		require.Equal(t, 2, len(m["app"].Outputs))
   347  		require.Equal(t, []string{"type=image", "type=docker"}, m["app"].Outputs)
   348  	})
   349  
   350  	t.Run("type image load false", func(t *testing.T) {
   351  		fp := File{
   352  			Name: "docker-bake.hcl",
   353  			Data: []byte(
   354  				`target "app" {
   355  				output = ["type=image"]
   356  			}`),
   357  		}
   358  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=false"}, nil)
   359  		require.NoError(t, err)
   360  		require.Equal(t, 1, len(m["app"].Outputs))
   361  		require.Equal(t, []string{"type=image"}, m["app"].Outputs)
   362  	})
   363  
   364  	t.Run("type registry", func(t *testing.T) {
   365  		fp := File{
   366  			Name: "docker-bake.hcl",
   367  			Data: []byte(
   368  				`target "app" {
   369  				output = ["type=registry"]
   370  			}`),
   371  		}
   372  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
   373  		require.NoError(t, err)
   374  		require.Equal(t, 2, len(m["app"].Outputs))
   375  		require.Equal(t, []string{"type=registry", "type=docker"}, m["app"].Outputs)
   376  	})
   377  
   378  	t.Run("type oci", func(t *testing.T) {
   379  		fp := File{
   380  			Name: "docker-bake.hcl",
   381  			Data: []byte(
   382  				`target "app" {
   383  				output = ["type=oci,dest=out"]
   384  			}`),
   385  		}
   386  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
   387  		require.NoError(t, err)
   388  		require.Equal(t, 2, len(m["app"].Outputs))
   389  		require.Equal(t, []string{"type=oci,dest=out", "type=docker"}, m["app"].Outputs)
   390  	})
   391  
   392  	t.Run("type docker with dest", func(t *testing.T) {
   393  		fp := File{
   394  			Name: "docker-bake.hcl",
   395  			Data: []byte(
   396  				`target "app" {
   397  				output = ["type=docker,dest=out"]
   398  			}`),
   399  		}
   400  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
   401  		require.NoError(t, err)
   402  		require.Equal(t, 2, len(m["app"].Outputs))
   403  		require.Equal(t, []string{"type=docker,dest=out", "type=docker"}, m["app"].Outputs)
   404  	})
   405  
   406  	t.Run("type local and empty target", func(t *testing.T) {
   407  		fp := File{
   408  			Name: "docker-bake.hcl",
   409  			Data: []byte(
   410  				`target "foo" {
   411  		  		output = [ "type=local,dest=out" ]
   412  			}
   413  			target "bar" {
   414  			}`),
   415  		}
   416  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo", "bar"}, []string{"*.load=true"}, nil)
   417  		require.NoError(t, err)
   418  		require.Equal(t, 2, len(m))
   419  		require.Equal(t, 1, len(m["foo"].Outputs))
   420  		require.Equal(t, []string{"type=local,dest=out"}, m["foo"].Outputs)
   421  		require.Equal(t, 1, len(m["bar"].Outputs))
   422  		require.Equal(t, []string{"type=docker"}, m["bar"].Outputs)
   423  	})
   424  }
   425  
   426  func TestLoadAndPushOverride(t *testing.T) {
   427  	t.Run("type local and empty target", func(t *testing.T) {
   428  		fp := File{
   429  			Name: "docker-bake.hcl",
   430  			Data: []byte(
   431  				`target "foo" {
   432  		  		output = [ "type=local,dest=out" ]
   433  			}
   434  			target "bar" {
   435  			}`),
   436  		}
   437  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo", "bar"}, []string{"*.load=true", "*.push=true"}, nil)
   438  		require.NoError(t, err)
   439  		require.Equal(t, 2, len(m))
   440  
   441  		require.Equal(t, 1, len(m["foo"].Outputs))
   442  		sort.Strings(m["foo"].Outputs)
   443  		require.Equal(t, []string{"type=local,dest=out"}, m["foo"].Outputs)
   444  
   445  		require.Equal(t, 2, len(m["bar"].Outputs))
   446  		sort.Strings(m["bar"].Outputs)
   447  		require.Equal(t, []string{"type=docker", "type=image,push=true"}, m["bar"].Outputs)
   448  	})
   449  
   450  	t.Run("type registry", func(t *testing.T) {
   451  		fp := File{
   452  			Name: "docker-bake.hcl",
   453  			Data: []byte(
   454  				`target "foo" {
   455  		  		output = [ "type=registry" ]
   456  			}`),
   457  		}
   458  		m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo"}, []string{"*.load=true", "*.push=true"}, nil)
   459  		require.NoError(t, err)
   460  		require.Equal(t, 1, len(m))
   461  
   462  		require.Equal(t, 2, len(m["foo"].Outputs))
   463  		sort.Strings(m["foo"].Outputs)
   464  		require.Equal(t, []string{"type=docker", "type=registry"}, m["foo"].Outputs)
   465  	})
   466  }
   467  
   468  func TestReadTargetsCompose(t *testing.T) {
   469  	t.Parallel()
   470  
   471  	fp := File{
   472  		Name: "docker-compose.yml",
   473  		Data: []byte(
   474  			`version: "3"
   475  services:
   476    db:
   477      build: .
   478      command: ./entrypoint.sh
   479      image: docker.io/tonistiigi/db
   480    webapp:
   481      build:
   482        dockerfile: Dockerfile.webapp
   483        args:
   484          buildno: 1
   485  `),
   486  	}
   487  
   488  	fp2 := File{
   489  		Name: "docker-compose2.yml",
   490  		Data: []byte(
   491  			`version: "3"
   492  services:
   493    newservice:
   494      build: .
   495    webapp:
   496      build:
   497        args:
   498          buildno2: 12
   499  `),
   500  	}
   501  
   502  	fp3 := File{
   503  		Name: "docker-compose3.yml",
   504  		Data: []byte(
   505  			`version: "3"
   506  services:
   507    webapp:
   508      entrypoint: echo 1
   509  `),
   510  	}
   511  
   512  	ctx := context.TODO()
   513  
   514  	m, g, err := ReadTargets(ctx, []File{fp, fp2, fp3}, []string{"default"}, nil, nil)
   515  	require.NoError(t, err)
   516  
   517  	require.Equal(t, 3, len(m))
   518  	_, ok := m["newservice"]
   519  
   520  	require.True(t, ok)
   521  	require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile)
   522  	require.Equal(t, ".", *m["webapp"].Context)
   523  	require.Equal(t, ptrstr("1"), m["webapp"].Args["buildno"])
   524  	require.Equal(t, ptrstr("12"), m["webapp"].Args["buildno2"])
   525  
   526  	require.Equal(t, 1, len(g))
   527  	sort.Strings(g["default"].Targets)
   528  	require.Equal(t, []string{"db", "newservice", "webapp"}, g["default"].Targets)
   529  }
   530  
   531  func TestReadTargetsWithDotCompose(t *testing.T) {
   532  	t.Parallel()
   533  
   534  	fp := File{
   535  		Name: "docker-compose.yml",
   536  		Data: []byte(
   537  			`version: "3"
   538  services:
   539    web.app:
   540      build:
   541        dockerfile: Dockerfile.webapp
   542        args:
   543          buildno: 1
   544  `),
   545  	}
   546  
   547  	fp2 := File{
   548  		Name: "docker-compose2.yml",
   549  		Data: []byte(
   550  			`version: "3"
   551  services:
   552    web_app:
   553      build:
   554        args:
   555          buildno2: 12
   556  `),
   557  	}
   558  
   559  	ctx := context.TODO()
   560  
   561  	m, _, err := ReadTargets(ctx, []File{fp}, []string{"web.app"}, nil, nil)
   562  	require.NoError(t, err)
   563  	require.Equal(t, 1, len(m))
   564  	_, ok := m["web_app"]
   565  	require.True(t, ok)
   566  	require.Equal(t, "Dockerfile.webapp", *m["web_app"].Dockerfile)
   567  	require.Equal(t, ptrstr("1"), m["web_app"].Args["buildno"])
   568  
   569  	m, _, err = ReadTargets(ctx, []File{fp2}, []string{"web_app"}, nil, nil)
   570  	require.NoError(t, err)
   571  	require.Equal(t, 1, len(m))
   572  	_, ok = m["web_app"]
   573  	require.True(t, ok)
   574  	require.Equal(t, "Dockerfile", *m["web_app"].Dockerfile)
   575  	require.Equal(t, ptrstr("12"), m["web_app"].Args["buildno2"])
   576  
   577  	m, g, err := ReadTargets(ctx, []File{fp, fp2}, []string{"default"}, nil, nil)
   578  	require.NoError(t, err)
   579  	require.Equal(t, 1, len(m))
   580  	_, ok = m["web_app"]
   581  	require.True(t, ok)
   582  	require.Equal(t, "Dockerfile.webapp", *m["web_app"].Dockerfile)
   583  	require.Equal(t, ".", *m["web_app"].Context)
   584  	require.Equal(t, ptrstr("1"), m["web_app"].Args["buildno"])
   585  	require.Equal(t, ptrstr("12"), m["web_app"].Args["buildno2"])
   586  
   587  	require.Equal(t, 1, len(g))
   588  	sort.Strings(g["default"].Targets)
   589  	require.Equal(t, []string{"web_app"}, g["default"].Targets)
   590  }
   591  
   592  func TestHCLContextCwdPrefix(t *testing.T) {
   593  	fp := File{
   594  		Name: "docker-bake.hcl",
   595  		Data: []byte(
   596  			`target "app" {
   597  				context = "cwd://foo"
   598  				dockerfile = "test"
   599  			}`),
   600  	}
   601  	ctx := context.TODO()
   602  	m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
   603  	require.NoError(t, err)
   604  
   605  	bo, err := TargetsToBuildOpt(m, &Input{})
   606  	require.NoError(t, err)
   607  
   608  	require.Equal(t, 1, len(g))
   609  	require.Equal(t, []string{"app"}, g["default"].Targets)
   610  
   611  	require.Equal(t, 1, len(m))
   612  	require.Contains(t, m, "app")
   613  	assert.Equal(t, "test", *m["app"].Dockerfile)
   614  	assert.Equal(t, "foo", *m["app"].Context)
   615  	assert.Equal(t, "foo/test", bo["app"].Inputs.DockerfilePath)
   616  	assert.Equal(t, "foo", bo["app"].Inputs.ContextPath)
   617  }
   618  
   619  func TestHCLDockerfileCwdPrefix(t *testing.T) {
   620  	fp := File{
   621  		Name: "docker-bake.hcl",
   622  		Data: []byte(
   623  			`target "app" {
   624  				context = "."
   625  				dockerfile = "cwd://Dockerfile.app"
   626  			}`),
   627  	}
   628  	ctx := context.TODO()
   629  
   630  	cwd, err := os.Getwd()
   631  	require.NoError(t, err)
   632  
   633  	m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
   634  	require.NoError(t, err)
   635  
   636  	bo, err := TargetsToBuildOpt(m, &Input{})
   637  	require.NoError(t, err)
   638  
   639  	require.Equal(t, 1, len(g))
   640  	require.Equal(t, []string{"app"}, g["default"].Targets)
   641  
   642  	require.Equal(t, 1, len(m))
   643  	require.Contains(t, m, "app")
   644  	assert.Equal(t, "cwd://Dockerfile.app", *m["app"].Dockerfile)
   645  	assert.Equal(t, ".", *m["app"].Context)
   646  	assert.Equal(t, filepath.Join(cwd, "Dockerfile.app"), bo["app"].Inputs.DockerfilePath)
   647  	assert.Equal(t, ".", bo["app"].Inputs.ContextPath)
   648  }
   649  
   650  func TestOverrideMerge(t *testing.T) {
   651  	fp := File{
   652  		Name: "docker-bake.hcl",
   653  		Data: []byte(
   654  			`target "app" {
   655  				platforms = ["linux/amd64"]
   656  				output = ["foo"]
   657  			}`),
   658  	}
   659  	ctx := context.TODO()
   660  	m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{
   661  		"app.platform=linux/arm",
   662  		"app.platform=linux/ppc64le",
   663  		"app.output=type=registry",
   664  	}, nil)
   665  	require.NoError(t, err)
   666  
   667  	require.Equal(t, 1, len(m))
   668  	_, ok := m["app"]
   669  	require.True(t, ok)
   670  
   671  	_, err = TargetsToBuildOpt(m, &Input{})
   672  	require.NoError(t, err)
   673  
   674  	require.Equal(t, []string{"linux/arm", "linux/ppc64le"}, m["app"].Platforms)
   675  	require.Equal(t, 1, len(m["app"].Outputs))
   676  	require.Equal(t, "type=registry", m["app"].Outputs[0])
   677  }
   678  
   679  func TestReadContexts(t *testing.T) {
   680  	fp := File{
   681  		Name: "docker-bake.hcl",
   682  		Data: []byte(`
   683  		target "base" {
   684  			contexts = {
   685  				foo: "bar"
   686  				abc: "def"
   687  			}
   688  		}
   689  		target "app" {
   690  			inherits = ["base"]
   691  			contexts = {
   692  				foo: "baz"
   693  			}
   694  		}
   695  		`),
   696  	}
   697  
   698  	ctx := context.TODO()
   699  	m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{}, nil)
   700  	require.NoError(t, err)
   701  
   702  	require.Equal(t, 1, len(m))
   703  	_, ok := m["app"]
   704  	require.True(t, ok)
   705  
   706  	bo, err := TargetsToBuildOpt(m, &Input{})
   707  	require.NoError(t, err)
   708  
   709  	ctxs := bo["app"].Inputs.NamedContexts
   710  	require.Equal(t, 2, len(ctxs))
   711  
   712  	require.Equal(t, "baz", ctxs["foo"].Path)
   713  	require.Equal(t, "def", ctxs["abc"].Path)
   714  
   715  	m, _, err = ReadTargets(ctx, []File{fp}, []string{"app"}, []string{"app.contexts.foo=bay", "base.contexts.ghi=jkl"}, nil)
   716  	require.NoError(t, err)
   717  
   718  	require.Equal(t, 1, len(m))
   719  	_, ok = m["app"]
   720  	require.True(t, ok)
   721  
   722  	bo, err = TargetsToBuildOpt(m, &Input{})
   723  	require.NoError(t, err)
   724  
   725  	ctxs = bo["app"].Inputs.NamedContexts
   726  	require.Equal(t, 3, len(ctxs))
   727  
   728  	require.Equal(t, "bay", ctxs["foo"].Path)
   729  	require.Equal(t, "def", ctxs["abc"].Path)
   730  	require.Equal(t, "jkl", ctxs["ghi"].Path)
   731  
   732  	// test resetting base values
   733  	m, _, err = ReadTargets(ctx, []File{fp}, []string{"app"}, []string{"app.contexts.foo="}, nil)
   734  	require.NoError(t, err)
   735  
   736  	require.Equal(t, 1, len(m))
   737  	_, ok = m["app"]
   738  	require.True(t, ok)
   739  
   740  	bo, err = TargetsToBuildOpt(m, &Input{})
   741  	require.NoError(t, err)
   742  
   743  	ctxs = bo["app"].Inputs.NamedContexts
   744  	require.Equal(t, 1, len(ctxs))
   745  	require.Equal(t, "def", ctxs["abc"].Path)
   746  }
   747  
   748  func TestReadContextFromTargetUnknown(t *testing.T) {
   749  	fp := File{
   750  		Name: "docker-bake.hcl",
   751  		Data: []byte(`
   752  		target "base" {
   753  			contexts = {
   754  				foo: "bar"
   755  				abc: "def"
   756  			}
   757  		}
   758  		target "app" {
   759  			contexts = {
   760  				foo: "baz"
   761  				bar: "target:bar"
   762  			}
   763  		}
   764  		`),
   765  	}
   766  
   767  	ctx := context.TODO()
   768  	_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{}, nil)
   769  	require.Error(t, err)
   770  	require.Contains(t, err.Error(), "failed to find target bar")
   771  }
   772  
   773  func TestReadEmptyTargets(t *testing.T) {
   774  	t.Parallel()
   775  
   776  	fp := File{
   777  		Name: "docker-bake.hcl",
   778  		Data: []byte(`target "app1" {}`),
   779  	}
   780  
   781  	fp2 := File{
   782  		Name: "docker-compose.yml",
   783  		Data: []byte(`
   784  services:
   785    app2:
   786      build: {}
   787  `),
   788  	}
   789  
   790  	ctx := context.TODO()
   791  
   792  	m, _, err := ReadTargets(ctx, []File{fp, fp2}, []string{"app1", "app2"}, nil, nil)
   793  	require.NoError(t, err)
   794  
   795  	require.Equal(t, 2, len(m))
   796  	_, ok := m["app1"]
   797  	require.True(t, ok)
   798  	_, ok = m["app2"]
   799  	require.True(t, ok)
   800  
   801  	require.Equal(t, "Dockerfile", *m["app1"].Dockerfile)
   802  	require.Equal(t, ".", *m["app1"].Context)
   803  	require.Equal(t, "Dockerfile", *m["app2"].Dockerfile)
   804  	require.Equal(t, ".", *m["app2"].Context)
   805  }
   806  
   807  func TestReadContextFromTargetChain(t *testing.T) {
   808  	ctx := context.TODO()
   809  	fp := File{
   810  		Name: "docker-bake.hcl",
   811  		Data: []byte(`
   812  		target "base" {
   813  		}
   814  		target "mid" {
   815  			output = ["foo"]
   816  			contexts = {
   817  				parent: "target:base"
   818  			}
   819  		}
   820  		target "app" {
   821  			contexts = {
   822  				foo: "baz"
   823  				bar: "target:mid"
   824  			}
   825  		}
   826  		target "unused" {}
   827  		`),
   828  	}
   829  
   830  	m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{}, nil)
   831  	require.NoError(t, err)
   832  
   833  	require.Equal(t, 3, len(m))
   834  	app, ok := m["app"]
   835  	require.True(t, ok)
   836  
   837  	require.Equal(t, 2, len(app.Contexts))
   838  
   839  	mid, ok := m["mid"]
   840  	require.True(t, ok)
   841  	require.Equal(t, 0, len(mid.Outputs))
   842  	require.Equal(t, 1, len(mid.Contexts))
   843  
   844  	base, ok := m["base"]
   845  	require.True(t, ok)
   846  	require.Equal(t, 0, len(base.Contexts))
   847  }
   848  
   849  func TestReadContextFromTargetInfiniteLoop(t *testing.T) {
   850  	ctx := context.TODO()
   851  	fp := File{
   852  		Name: "docker-bake.hcl",
   853  		Data: []byte(`
   854  		target "mid" {
   855  			output = ["foo"]
   856  			contexts = {
   857  				parent: "target:app"
   858  			}
   859  		}
   860  		target "app" {
   861  			contexts = {
   862  				foo: "baz"
   863  				bar: "target:mid"
   864  			}
   865  		}
   866  		`),
   867  	}
   868  	_, _, err := ReadTargets(ctx, []File{fp}, []string{"app", "mid"}, []string{}, nil)
   869  	require.Error(t, err)
   870  	require.Contains(t, err.Error(), "infinite loop from")
   871  }
   872  
   873  func TestReadContextFromTargetMultiPlatform(t *testing.T) {
   874  	ctx := context.TODO()
   875  	fp := File{
   876  		Name: "docker-bake.hcl",
   877  		Data: []byte(`
   878  		target "mid" {
   879  			output = ["foo"]
   880  			platforms = ["linux/amd64", "linux/arm64"]
   881  		}
   882  		target "app" {
   883  			contexts = {
   884  				bar: "target:mid"
   885  			}
   886  			platforms = ["linux/amd64", "linux/arm64"]
   887  		}
   888  		`),
   889  	}
   890  	_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{}, nil)
   891  	require.NoError(t, err)
   892  }
   893  
   894  func TestReadContextFromTargetInvalidPlatforms(t *testing.T) {
   895  	ctx := context.TODO()
   896  	fp := File{
   897  		Name: "docker-bake.hcl",
   898  		Data: []byte(`
   899  		target "mid" {
   900  			output = ["foo"]
   901  			platforms = ["linux/amd64", "linux/riscv64"]
   902  		}
   903  		target "app" {
   904  			contexts = {
   905  				bar: "target:mid"
   906  			}
   907  			platforms = ["linux/amd64", "linux/arm64"]
   908  		}
   909  		`),
   910  	}
   911  	_, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{}, nil)
   912  	require.Error(t, err)
   913  	require.Contains(t, err.Error(), "defined for different platforms")
   914  }
   915  
   916  func TestReadTargetsDefault(t *testing.T) {
   917  	t.Parallel()
   918  	ctx := context.TODO()
   919  
   920  	f := File{
   921  		Name: "docker-bake.hcl",
   922  		Data: []byte(`
   923  target "default" {
   924    dockerfile = "test"
   925  }`)}
   926  
   927  	m, g, err := ReadTargets(ctx, []File{f}, []string{"default"}, nil, nil)
   928  	require.NoError(t, err)
   929  	require.Equal(t, 0, len(g))
   930  	require.Equal(t, 1, len(m))
   931  	require.Equal(t, "test", *m["default"].Dockerfile)
   932  }
   933  
   934  func TestReadTargetsSpecified(t *testing.T) {
   935  	t.Parallel()
   936  	ctx := context.TODO()
   937  
   938  	f := File{
   939  		Name: "docker-bake.hcl",
   940  		Data: []byte(`
   941  target "image" {
   942    dockerfile = "test"
   943  }`)}
   944  
   945  	_, _, err := ReadTargets(ctx, []File{f}, []string{"default"}, nil, nil)
   946  	require.Error(t, err)
   947  
   948  	m, g, err := ReadTargets(ctx, []File{f}, []string{"image"}, nil, nil)
   949  	require.NoError(t, err)
   950  	require.Equal(t, 1, len(g))
   951  	require.Equal(t, []string{"image"}, g["default"].Targets)
   952  	require.Equal(t, 1, len(m))
   953  	require.Equal(t, "test", *m["image"].Dockerfile)
   954  }
   955  
   956  func TestReadTargetsGroup(t *testing.T) {
   957  	t.Parallel()
   958  	ctx := context.TODO()
   959  
   960  	f := File{
   961  		Name: "docker-bake.hcl",
   962  		Data: []byte(`
   963  group "foo" {
   964    targets = ["image"]
   965  }
   966  target "image" {
   967    dockerfile = "test"
   968  }`)}
   969  
   970  	m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil)
   971  	require.NoError(t, err)
   972  	require.Equal(t, 2, len(g))
   973  	require.Equal(t, []string{"foo"}, g["default"].Targets)
   974  	require.Equal(t, []string{"image"}, g["foo"].Targets)
   975  	require.Equal(t, 1, len(m))
   976  	require.Equal(t, "test", *m["image"].Dockerfile)
   977  }
   978  
   979  func TestReadTargetsGroupAndTarget(t *testing.T) {
   980  	t.Parallel()
   981  	ctx := context.TODO()
   982  
   983  	f := File{
   984  		Name: "docker-bake.hcl",
   985  		Data: []byte(`
   986  group "foo" {
   987    targets = ["image"]
   988  }
   989  target "foo" {
   990    dockerfile = "bar"
   991  }
   992  target "image" {
   993    dockerfile = "test"
   994  }`)}
   995  
   996  	m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil)
   997  	require.NoError(t, err)
   998  	require.Equal(t, 2, len(g))
   999  	require.Equal(t, []string{"foo"}, g["default"].Targets)
  1000  	require.Equal(t, []string{"image"}, g["foo"].Targets)
  1001  	require.Equal(t, 1, len(m))
  1002  	require.Equal(t, "test", *m["image"].Dockerfile)
  1003  
  1004  	m, g, err = ReadTargets(ctx, []File{f}, []string{"foo", "foo"}, nil, nil)
  1005  	require.NoError(t, err)
  1006  	require.Equal(t, 2, len(g))
  1007  	require.Equal(t, []string{"foo"}, g["default"].Targets)
  1008  	require.Equal(t, []string{"image"}, g["foo"].Targets)
  1009  	require.Equal(t, 1, len(m))
  1010  	require.Equal(t, "test", *m["image"].Dockerfile)
  1011  }
  1012  
  1013  func TestReadTargetsMixed(t *testing.T) {
  1014  	t.Parallel()
  1015  	ctx := context.TODO()
  1016  
  1017  	fhcl := File{
  1018  		Name: "docker-bake.hcl",
  1019  		Data: []byte(`
  1020  group "default" {
  1021    targets = ["image"]
  1022  }
  1023  target "nocache" {
  1024    no-cache = true
  1025  }
  1026  group "release" {
  1027    targets = ["image-release"]
  1028  }
  1029  target "image" {
  1030    inherits = ["nocache"]
  1031    output = ["type=docker"]
  1032  }
  1033  target "image-release" {
  1034    inherits = ["image"]
  1035    output = ["type=image,push=true"]
  1036    tags = ["user/app:latest"]
  1037  }`)}
  1038  
  1039  	fyml := File{
  1040  		Name: "docker-compose.yml",
  1041  		Data: []byte(`
  1042  services:
  1043    addon:
  1044      build:
  1045        context: .
  1046        dockerfile: ./Dockerfile
  1047        args:
  1048          CT_ECR: foo
  1049          CT_TAG: bar
  1050      image: ct-addon:bar
  1051      environment:
  1052        - NODE_ENV=test
  1053        - AWS_ACCESS_KEY_ID=dummy
  1054        - AWS_SECRET_ACCESS_KEY=dummy
  1055    aws:
  1056      build:
  1057        dockerfile: ./aws.Dockerfile
  1058        args:
  1059          CT_ECR: foo
  1060          CT_TAG: bar
  1061      image: ct-fake-aws:bar`)}
  1062  
  1063  	fjson := File{
  1064  		Name: "docker-bake.json",
  1065  		Data: []byte(`{
  1066  	 "group": {
  1067  	   "default": {
  1068  	     "targets": [
  1069  	       "image"
  1070  	     ]
  1071  	   }
  1072  	 },
  1073  	 "target": {
  1074  	   "image": {
  1075  	     "context": ".",
  1076  	     "dockerfile": "Dockerfile",
  1077  	     "output": [
  1078  	       "type=docker"
  1079  	     ]
  1080  	   }
  1081  	 }
  1082  	}`)}
  1083  
  1084  	m, g, err := ReadTargets(ctx, []File{fhcl}, []string{"default"}, nil, nil)
  1085  	require.NoError(t, err)
  1086  	require.Equal(t, 1, len(g))
  1087  	require.Equal(t, []string{"image"}, g["default"].Targets)
  1088  	require.Equal(t, 1, len(m))
  1089  	require.Equal(t, 1, len(m["image"].Outputs))
  1090  	require.Equal(t, "type=docker", m["image"].Outputs[0])
  1091  
  1092  	m, g, err = ReadTargets(ctx, []File{fhcl}, []string{"image-release"}, nil, nil)
  1093  	require.NoError(t, err)
  1094  	require.Equal(t, 1, len(g))
  1095  	require.Equal(t, []string{"image-release"}, g["default"].Targets)
  1096  	require.Equal(t, 1, len(m))
  1097  	require.Equal(t, 1, len(m["image-release"].Outputs))
  1098  	require.Equal(t, "type=image,push=true", m["image-release"].Outputs[0])
  1099  
  1100  	m, g, err = ReadTargets(ctx, []File{fhcl}, []string{"image", "image-release"}, nil, nil)
  1101  	require.NoError(t, err)
  1102  	require.Equal(t, 1, len(g))
  1103  	require.Equal(t, []string{"image", "image-release"}, g["default"].Targets)
  1104  	require.Equal(t, 2, len(m))
  1105  	require.Equal(t, ".", *m["image"].Context)
  1106  	require.Equal(t, 1, len(m["image-release"].Outputs))
  1107  	require.Equal(t, "type=image,push=true", m["image-release"].Outputs[0])
  1108  
  1109  	m, g, err = ReadTargets(ctx, []File{fyml, fhcl}, []string{"default"}, nil, nil)
  1110  	require.NoError(t, err)
  1111  	require.Equal(t, 1, len(g))
  1112  	require.Equal(t, []string{"image"}, g["default"].Targets)
  1113  	require.Equal(t, 1, len(m))
  1114  	require.Equal(t, ".", *m["image"].Context)
  1115  
  1116  	m, g, err = ReadTargets(ctx, []File{fjson}, []string{"default"}, nil, nil)
  1117  	require.NoError(t, err)
  1118  	require.Equal(t, 1, len(g))
  1119  	require.Equal(t, []string{"image"}, g["default"].Targets)
  1120  	require.Equal(t, 1, len(m))
  1121  	require.Equal(t, ".", *m["image"].Context)
  1122  
  1123  	m, g, err = ReadTargets(ctx, []File{fyml}, []string{"default"}, nil, nil)
  1124  	require.NoError(t, err)
  1125  	require.Equal(t, 1, len(g))
  1126  	sort.Strings(g["default"].Targets)
  1127  	require.Equal(t, []string{"addon", "aws"}, g["default"].Targets)
  1128  	require.Equal(t, 2, len(m))
  1129  	require.Equal(t, "./Dockerfile", *m["addon"].Dockerfile)
  1130  	require.Equal(t, "./aws.Dockerfile", *m["aws"].Dockerfile)
  1131  
  1132  	m, g, err = ReadTargets(ctx, []File{fyml, fhcl}, []string{"addon", "aws"}, nil, nil)
  1133  	require.NoError(t, err)
  1134  	require.Equal(t, 1, len(g))
  1135  	sort.Strings(g["default"].Targets)
  1136  	require.Equal(t, []string{"addon", "aws"}, g["default"].Targets)
  1137  	require.Equal(t, 2, len(m))
  1138  	require.Equal(t, "./Dockerfile", *m["addon"].Dockerfile)
  1139  	require.Equal(t, "./aws.Dockerfile", *m["aws"].Dockerfile)
  1140  
  1141  	m, g, err = ReadTargets(ctx, []File{fyml, fhcl}, []string{"addon", "aws", "image"}, nil, nil)
  1142  	require.NoError(t, err)
  1143  	require.Equal(t, 1, len(g))
  1144  	sort.Strings(g["default"].Targets)
  1145  	require.Equal(t, []string{"addon", "aws", "image"}, g["default"].Targets)
  1146  	require.Equal(t, 3, len(m))
  1147  	require.Equal(t, ".", *m["image"].Context)
  1148  	require.Equal(t, "./Dockerfile", *m["addon"].Dockerfile)
  1149  	require.Equal(t, "./aws.Dockerfile", *m["aws"].Dockerfile)
  1150  }
  1151  
  1152  func TestReadTargetsSameGroupTarget(t *testing.T) {
  1153  	t.Parallel()
  1154  	ctx := context.TODO()
  1155  
  1156  	f := File{
  1157  		Name: "docker-bake.hcl",
  1158  		Data: []byte(`
  1159  group "foo" {
  1160    targets = ["foo"]
  1161  }
  1162  target "foo" {
  1163    dockerfile = "bar"
  1164  }
  1165  target "image" {
  1166    output = ["type=docker"]
  1167  }`)}
  1168  
  1169  	m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil)
  1170  	require.NoError(t, err)
  1171  	require.Equal(t, 2, len(g))
  1172  	require.Equal(t, []string{"foo"}, g["default"].Targets)
  1173  	require.Equal(t, []string{"foo"}, g["foo"].Targets)
  1174  	require.Equal(t, 1, len(m))
  1175  	require.Equal(t, "bar", *m["foo"].Dockerfile)
  1176  
  1177  	m, g, err = ReadTargets(ctx, []File{f}, []string{"foo", "foo"}, nil, nil)
  1178  	require.NoError(t, err)
  1179  	require.Equal(t, 2, len(g))
  1180  	require.Equal(t, []string{"foo"}, g["default"].Targets)
  1181  	require.Equal(t, []string{"foo"}, g["foo"].Targets)
  1182  	require.Equal(t, 1, len(m))
  1183  	require.Equal(t, "bar", *m["foo"].Dockerfile)
  1184  }
  1185  
  1186  func TestReadTargetsSameGroupTargetMulti(t *testing.T) {
  1187  	t.Parallel()
  1188  	ctx := context.TODO()
  1189  
  1190  	f := File{
  1191  		Name: "docker-bake.hcl",
  1192  		Data: []byte(`
  1193  group "foo" {
  1194    targets = ["foo", "image"]
  1195  }
  1196  target "foo" {
  1197    dockerfile = "bar"
  1198  }
  1199  target "image" {
  1200    output = ["type=docker"]
  1201  }`)}
  1202  
  1203  	m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil)
  1204  	require.NoError(t, err)
  1205  	require.Equal(t, 2, len(g))
  1206  	require.Equal(t, []string{"foo"}, g["default"].Targets)
  1207  	require.Equal(t, []string{"foo", "image"}, g["foo"].Targets)
  1208  	require.Equal(t, 2, len(m))
  1209  	require.Equal(t, "bar", *m["foo"].Dockerfile)
  1210  	require.Equal(t, "type=docker", m["image"].Outputs[0])
  1211  
  1212  	m, g, err = ReadTargets(ctx, []File{f}, []string{"foo", "image"}, nil, nil)
  1213  	require.NoError(t, err)
  1214  	require.Equal(t, 2, len(g))
  1215  	require.Equal(t, []string{"foo", "image"}, g["default"].Targets)
  1216  	require.Equal(t, []string{"foo", "image"}, g["foo"].Targets)
  1217  	require.Equal(t, 2, len(m))
  1218  	require.Equal(t, "bar", *m["foo"].Dockerfile)
  1219  	require.Equal(t, "type=docker", m["image"].Outputs[0])
  1220  }
  1221  
  1222  func TestNestedInherits(t *testing.T) {
  1223  	ctx := context.TODO()
  1224  
  1225  	f := File{
  1226  		Name: "docker-bake.hcl",
  1227  		Data: []byte(`
  1228  target "a" {
  1229    args = {
  1230      foo = "123"
  1231      bar = "234"
  1232    }
  1233  }
  1234  target "b" {
  1235    inherits = ["a"]
  1236    args = {
  1237      bar = "567"
  1238    }
  1239  }
  1240  target "c" {
  1241    inherits = ["a"]
  1242    args = {
  1243      baz = "890"
  1244    }
  1245  }
  1246  target "d" {
  1247    inherits = ["b", "c"]
  1248  }`)}
  1249  
  1250  	cases := []struct {
  1251  		name      string
  1252  		overrides []string
  1253  		want      map[string]*string
  1254  	}{
  1255  		{
  1256  			name:      "nested simple",
  1257  			overrides: nil,
  1258  			want:      map[string]*string{"bar": ptrstr("234"), "baz": ptrstr("890"), "foo": ptrstr("123")},
  1259  		},
  1260  		{
  1261  			name:      "nested with overrides first",
  1262  			overrides: []string{"a.args.foo=321", "b.args.bar=432"},
  1263  			want:      map[string]*string{"bar": ptrstr("234"), "baz": ptrstr("890"), "foo": ptrstr("321")},
  1264  		},
  1265  		{
  1266  			name:      "nested with overrides last",
  1267  			overrides: []string{"a.args.foo=321", "c.args.bar=432"},
  1268  			want:      map[string]*string{"bar": ptrstr("432"), "baz": ptrstr("890"), "foo": ptrstr("321")},
  1269  		},
  1270  	}
  1271  	for _, tt := range cases {
  1272  		tt := tt
  1273  		t.Run(tt.name, func(t *testing.T) {
  1274  			m, g, err := ReadTargets(ctx, []File{f}, []string{"d"}, tt.overrides, nil)
  1275  			require.NoError(t, err)
  1276  			require.Equal(t, 1, len(g))
  1277  			require.Equal(t, []string{"d"}, g["default"].Targets)
  1278  			require.Equal(t, 1, len(m))
  1279  			require.Equal(t, tt.want, m["d"].Args)
  1280  		})
  1281  	}
  1282  }
  1283  
  1284  func TestNestedInheritsWithGroup(t *testing.T) {
  1285  	ctx := context.TODO()
  1286  
  1287  	f := File{
  1288  		Name: "docker-bake.hcl",
  1289  		Data: []byte(`
  1290  target "grandparent" {
  1291    output = ["type=docker"]
  1292    args = {
  1293      BAR = "fuu"
  1294    }
  1295  }
  1296  target "parent" {
  1297    inherits = ["grandparent"]
  1298    args = {
  1299      FOO = "bar"
  1300    }
  1301  }
  1302  target "child1" {
  1303    inherits = ["parent"]
  1304  }
  1305  target "child2" {
  1306    inherits = ["parent"]
  1307    args = {
  1308      FOO2 = "bar2"
  1309    }
  1310  }
  1311  group "default" {
  1312    targets = [
  1313      "child1",
  1314      "child2"
  1315    ]
  1316  }`)}
  1317  
  1318  	cases := []struct {
  1319  		name      string
  1320  		overrides []string
  1321  		wantch1   map[string]*string
  1322  		wantch2   map[string]*string
  1323  	}{
  1324  		{
  1325  			name:      "nested simple",
  1326  			overrides: nil,
  1327  			wantch1:   map[string]*string{"BAR": ptrstr("fuu"), "FOO": ptrstr("bar")},
  1328  			wantch2:   map[string]*string{"BAR": ptrstr("fuu"), "FOO": ptrstr("bar"), "FOO2": ptrstr("bar2")},
  1329  		},
  1330  		{
  1331  			name:      "nested with overrides first",
  1332  			overrides: []string{"grandparent.args.BAR=fii", "child1.args.FOO=baaar"},
  1333  			wantch1:   map[string]*string{"BAR": ptrstr("fii"), "FOO": ptrstr("baaar")},
  1334  			wantch2:   map[string]*string{"BAR": ptrstr("fii"), "FOO": ptrstr("bar"), "FOO2": ptrstr("bar2")},
  1335  		},
  1336  		{
  1337  			name:      "nested with overrides last",
  1338  			overrides: []string{"grandparent.args.BAR=fii", "child2.args.FOO=baaar"},
  1339  			wantch1:   map[string]*string{"BAR": ptrstr("fii"), "FOO": ptrstr("bar")},
  1340  			wantch2:   map[string]*string{"BAR": ptrstr("fii"), "FOO": ptrstr("baaar"), "FOO2": ptrstr("bar2")},
  1341  		},
  1342  	}
  1343  	for _, tt := range cases {
  1344  		tt := tt
  1345  		t.Run(tt.name, func(t *testing.T) {
  1346  			m, g, err := ReadTargets(ctx, []File{f}, []string{"default"}, tt.overrides, nil)
  1347  			require.NoError(t, err)
  1348  			require.Equal(t, 1, len(g))
  1349  			require.Equal(t, []string{"child1", "child2"}, g["default"].Targets)
  1350  			require.Equal(t, 2, len(m))
  1351  			require.Equal(t, tt.wantch1, m["child1"].Args)
  1352  			require.Equal(t, []string{"type=docker"}, m["child1"].Outputs)
  1353  			require.Equal(t, tt.wantch2, m["child2"].Args)
  1354  			require.Equal(t, []string{"type=docker"}, m["child2"].Outputs)
  1355  		})
  1356  	}
  1357  }
  1358  
  1359  func TestTargetName(t *testing.T) {
  1360  	ctx := context.TODO()
  1361  	cases := []struct {
  1362  		target  string
  1363  		wantErr bool
  1364  	}{
  1365  		{
  1366  			target:  "a",
  1367  			wantErr: false,
  1368  		},
  1369  		{
  1370  			target:  "abc",
  1371  			wantErr: false,
  1372  		},
  1373  		{
  1374  			target:  "a/b",
  1375  			wantErr: true,
  1376  		},
  1377  		{
  1378  			target:  "a.b",
  1379  			wantErr: true,
  1380  		},
  1381  		{
  1382  			target:  "_a",
  1383  			wantErr: false,
  1384  		},
  1385  		{
  1386  			target:  "a_b",
  1387  			wantErr: false,
  1388  		},
  1389  		{
  1390  			target:  "AbC",
  1391  			wantErr: false,
  1392  		},
  1393  		{
  1394  			target:  "AbC-0123",
  1395  			wantErr: false,
  1396  		},
  1397  	}
  1398  	for _, tt := range cases {
  1399  		tt := tt
  1400  		t.Run(tt.target, func(t *testing.T) {
  1401  			_, _, err := ReadTargets(ctx, []File{{
  1402  				Name: "docker-bake.hcl",
  1403  				Data: []byte(`target "` + tt.target + `" {}`),
  1404  			}}, []string{tt.target}, nil, nil)
  1405  			if tt.wantErr {
  1406  				require.Error(t, err)
  1407  			} else {
  1408  				require.NoError(t, err)
  1409  			}
  1410  		})
  1411  	}
  1412  }
  1413  
  1414  func TestNestedGroupsWithSameTarget(t *testing.T) {
  1415  	ctx := context.TODO()
  1416  
  1417  	f := File{
  1418  		Name: "docker-bake.hcl",
  1419  		Data: []byte(`
  1420  group "a" {
  1421    targets = ["b", "c"]
  1422  }
  1423  
  1424  group "b" {
  1425    targets = ["d"]
  1426  }
  1427  
  1428  group "c" {
  1429    targets = ["b"]
  1430  }
  1431  
  1432  target "d" {
  1433    context = "."
  1434    dockerfile = "./testdockerfile"
  1435  }
  1436  
  1437  group "e" {
  1438    targets = ["a", "f"]
  1439  }
  1440  
  1441  target "f" {
  1442    context = "./foo"
  1443  }`)}
  1444  
  1445  	cases := []struct {
  1446  		names   []string
  1447  		targets []string
  1448  		groups  []string
  1449  		count   int
  1450  	}{
  1451  		{
  1452  			names:   []string{"a"},
  1453  			targets: []string{"a"},
  1454  			groups:  []string{"default", "a", "b", "c"},
  1455  			count:   1,
  1456  		},
  1457  		{
  1458  			names:   []string{"b"},
  1459  			targets: []string{"b"},
  1460  			groups:  []string{"default", "b"},
  1461  			count:   1,
  1462  		},
  1463  		{
  1464  			names:   []string{"c"},
  1465  			targets: []string{"c"},
  1466  			groups:  []string{"default", "b", "c"},
  1467  			count:   1,
  1468  		},
  1469  		{
  1470  			names:   []string{"d"},
  1471  			targets: []string{"d"},
  1472  			groups:  []string{"default"},
  1473  			count:   1,
  1474  		},
  1475  		{
  1476  			names:   []string{"e"},
  1477  			targets: []string{"e"},
  1478  			groups:  []string{"default", "a", "b", "c", "e"},
  1479  			count:   2,
  1480  		},
  1481  		{
  1482  			names:   []string{"a", "e"},
  1483  			targets: []string{"a", "e"},
  1484  			groups:  []string{"default", "a", "b", "c", "e"},
  1485  			count:   2,
  1486  		},
  1487  	}
  1488  	for _, tt := range cases {
  1489  		tt := tt
  1490  		t.Run(strings.Join(tt.names, "+"), func(t *testing.T) {
  1491  			m, g, err := ReadTargets(ctx, []File{f}, tt.names, nil, nil)
  1492  			require.NoError(t, err)
  1493  
  1494  			var gnames []string
  1495  			for _, g := range g {
  1496  				gnames = append(gnames, g.Name)
  1497  			}
  1498  			sort.Strings(gnames)
  1499  			sort.Strings(tt.groups)
  1500  			require.Equal(t, tt.groups, gnames)
  1501  
  1502  			sort.Strings(g["default"].Targets)
  1503  			sort.Strings(tt.targets)
  1504  			require.Equal(t, tt.targets, g["default"].Targets)
  1505  
  1506  			require.Equal(t, tt.count, len(m))
  1507  			require.Equal(t, ".", *m["d"].Context)
  1508  			require.Equal(t, "./testdockerfile", *m["d"].Dockerfile)
  1509  		})
  1510  	}
  1511  }
  1512  
  1513  func TestUnknownExt(t *testing.T) {
  1514  	dt := []byte(`
  1515  		target "app" {
  1516  			context = "dir"
  1517  			args = {
  1518  				v1 = "foo"
  1519  			}
  1520  		}
  1521  		`)
  1522  	dt2 := []byte(`
  1523  services:
  1524    app:
  1525      build:
  1526        dockerfile: Dockerfile-alternate
  1527        args:
  1528          v2: "bar"
  1529  `)
  1530  
  1531  	c, err := ParseFiles([]File{
  1532  		{Data: dt, Name: "c1.foo"},
  1533  		{Data: dt2, Name: "c2.bar"},
  1534  	}, nil)
  1535  	require.NoError(t, err)
  1536  
  1537  	require.Equal(t, 1, len(c.Targets))
  1538  	require.Equal(t, "app", c.Targets[0].Name)
  1539  	require.Equal(t, ptrstr("foo"), c.Targets[0].Args["v1"])
  1540  	require.Equal(t, ptrstr("bar"), c.Targets[0].Args["v2"])
  1541  	require.Equal(t, "dir", *c.Targets[0].Context)
  1542  	require.Equal(t, "Dockerfile-alternate", *c.Targets[0].Dockerfile)
  1543  }
  1544  
  1545  func TestHCLNullVars(t *testing.T) {
  1546  	fp := File{
  1547  		Name: "docker-bake.hcl",
  1548  		Data: []byte(
  1549  			`variable "FOO" {
  1550  				default = null
  1551  			}
  1552  			variable "BAR" {
  1553  				default = null
  1554  			}
  1555  			target "default" {
  1556  				args = {
  1557  					foo = FOO
  1558  					bar = "baz"
  1559  				}
  1560  				labels = {
  1561  					"com.docker.app.bar" = BAR
  1562  					"com.docker.app.baz" = "foo"
  1563  				}
  1564  			}`),
  1565  	}
  1566  
  1567  	ctx := context.TODO()
  1568  	m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil)
  1569  	require.NoError(t, err)
  1570  
  1571  	require.Equal(t, 1, len(m))
  1572  	_, ok := m["default"]
  1573  	require.True(t, ok)
  1574  
  1575  	_, err = TargetsToBuildOpt(m, &Input{})
  1576  	require.NoError(t, err)
  1577  	require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, m["default"].Args)
  1578  	require.Equal(t, map[string]*string{"com.docker.app.baz": ptrstr("foo")}, m["default"].Labels)
  1579  }
  1580  
  1581  func TestJSONNullVars(t *testing.T) {
  1582  	fp := File{
  1583  		Name: "docker-bake.json",
  1584  		Data: []byte(
  1585  			`{
  1586  				"variable": {
  1587  					"FOO": {
  1588  						"default": null
  1589  					}
  1590  				},
  1591  				"target": {
  1592  					"default": {
  1593  						"args": {
  1594  							"foo": "${FOO}",
  1595  							"bar": "baz"
  1596  						}
  1597  					}
  1598  				}
  1599  			}`),
  1600  	}
  1601  
  1602  	ctx := context.TODO()
  1603  	m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil)
  1604  	require.NoError(t, err)
  1605  
  1606  	require.Equal(t, 1, len(m))
  1607  	_, ok := m["default"]
  1608  	require.True(t, ok)
  1609  
  1610  	_, err = TargetsToBuildOpt(m, &Input{})
  1611  	require.NoError(t, err)
  1612  	require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, m["default"].Args)
  1613  }
  1614  
  1615  func TestReadLocalFilesDefault(t *testing.T) {
  1616  	tests := []struct {
  1617  		filenames []string
  1618  		expected  []string
  1619  	}{
  1620  		{
  1621  			filenames: []string{"abc.yml", "docker-compose.yml"},
  1622  			expected:  []string{"docker-compose.yml"},
  1623  		},
  1624  		{
  1625  			filenames: []string{"test.foo", "compose.yml", "docker-bake.hcl"},
  1626  			expected:  []string{"compose.yml", "docker-bake.hcl"},
  1627  		},
  1628  		{
  1629  			filenames: []string{"compose.yaml", "docker-compose.yml", "docker-bake.hcl"},
  1630  			expected:  []string{"compose.yaml", "docker-compose.yml", "docker-bake.hcl"},
  1631  		},
  1632  		{
  1633  			filenames: []string{"test.txt", "compsoe.yaml"}, // intentional misspell
  1634  			expected:  []string{},
  1635  		},
  1636  	}
  1637  	pwd, err := os.Getwd()
  1638  	require.NoError(t, err)
  1639  
  1640  	for _, tt := range tests {
  1641  		t.Run(strings.Join(tt.filenames, "-"), func(t *testing.T) {
  1642  			dir := t.TempDir()
  1643  			t.Cleanup(func() { _ = os.Chdir(pwd) })
  1644  			require.NoError(t, os.Chdir(dir))
  1645  			for _, tf := range tt.filenames {
  1646  				require.NoError(t, os.WriteFile(tf, []byte(tf), 0644))
  1647  			}
  1648  			files, err := ReadLocalFiles(nil, nil, nil)
  1649  			require.NoError(t, err)
  1650  			if len(files) == 0 {
  1651  				require.Equal(t, len(tt.expected), len(files))
  1652  			} else {
  1653  				found := false
  1654  				for _, exp := range tt.expected {
  1655  					for _, f := range files {
  1656  						if f.Name == exp {
  1657  							found = true
  1658  							break
  1659  						}
  1660  					}
  1661  					require.True(t, found, exp)
  1662  				}
  1663  			}
  1664  		})
  1665  	}
  1666  }
  1667  
  1668  func TestAttestDuplicates(t *testing.T) {
  1669  	fp := File{
  1670  		Name: "docker-bake.hcl",
  1671  		Data: []byte(
  1672  			`target "default" {
  1673                  attest = ["type=sbom", "type=sbom,generator=custom", "type=sbom,foo=bar", "type=provenance,mode=max"]
  1674              }`),
  1675  	}
  1676  	ctx := context.TODO()
  1677  
  1678  	m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil)
  1679  	require.Equal(t, []string{"type=sbom,foo=bar", "type=provenance,mode=max"}, m["default"].Attest)
  1680  	require.NoError(t, err)
  1681  
  1682  	opts, err := TargetsToBuildOpt(m, &Input{})
  1683  	require.NoError(t, err)
  1684  	require.Equal(t, map[string]*string{
  1685  		"sbom":       ptrstr("type=sbom,foo=bar"),
  1686  		"provenance": ptrstr("type=provenance,mode=max"),
  1687  	}, opts["default"].Attests)
  1688  
  1689  	m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,disabled=true"}, nil)
  1690  	require.Equal(t, []string{"type=sbom,disabled=true", "type=provenance,mode=max"}, m["default"].Attest)
  1691  	require.NoError(t, err)
  1692  
  1693  	opts, err = TargetsToBuildOpt(m, &Input{})
  1694  	require.NoError(t, err)
  1695  	require.Equal(t, map[string]*string{
  1696  		"sbom":       nil,
  1697  		"provenance": ptrstr("type=provenance,mode=max"),
  1698  	}, opts["default"].Attests)
  1699  }
  1700  
  1701  func TestAnnotations(t *testing.T) {
  1702  	fp := File{
  1703  		Name: "docker-bake.hcl",
  1704  		Data: []byte(
  1705  			`target "app" {
  1706  				output = ["type=image,name=foo"]
  1707  				annotations = ["manifest[linux/amd64]:foo=bar"]
  1708  			}`),
  1709  	}
  1710  	ctx := context.TODO()
  1711  	m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
  1712  	require.NoError(t, err)
  1713  
  1714  	bo, err := TargetsToBuildOpt(m, &Input{})
  1715  	require.NoError(t, err)
  1716  
  1717  	require.Equal(t, 1, len(g))
  1718  	require.Equal(t, []string{"app"}, g["default"].Targets)
  1719  
  1720  	require.Equal(t, 1, len(m))
  1721  	require.Contains(t, m, "app")
  1722  	require.Equal(t, "type=image,name=foo", m["app"].Outputs[0])
  1723  	require.Equal(t, "manifest[linux/amd64]:foo=bar", m["app"].Annotations[0])
  1724  
  1725  	require.Len(t, bo["app"].Exports, 1)
  1726  	require.Equal(t, "bar", bo["app"].Exports[0].Attrs["annotation-manifest[linux/amd64].foo"])
  1727  }