get.porter.sh/porter@v1.3.0/pkg/build/buildkit/buildx_test.go (about)

     1  package buildkit
     2  
     3  import (
     4  	"context"
     5  	_ "embed"
     6  	"testing"
     7  
     8  	"get.porter.sh/porter/pkg/build"
     9  	"get.porter.sh/porter/pkg/config"
    10  	"get.porter.sh/porter/pkg/manifest"
    11  	buildx "github.com/docker/buildx/build"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func Test_parseBuildArgs(t *testing.T) {
    17  	testcases := []struct {
    18  		name      string
    19  		inputArgs []string
    20  		wantArgs  map[string]string
    21  	}{
    22  		{name: "valid args", inputArgs: []string{"A=1", "B=2=2", "C="},
    23  			wantArgs: map[string]string{"A": "1", "B": "2=2", "C": ""}},
    24  		{name: "missing equal sign", inputArgs: []string{"A"},
    25  			wantArgs: map[string]string{}},
    26  	}
    27  
    28  	for _, tc := range testcases {
    29  		t.Run(tc.name, func(t *testing.T) {
    30  			var gotArgs = map[string]string{}
    31  			parseBuildArgs(tc.inputArgs, gotArgs)
    32  			assert.Equal(t, tc.wantArgs, gotArgs)
    33  		})
    34  	}
    35  }
    36  
    37  func Test_toNamedContexts(t *testing.T) {
    38  	testcases := []struct {
    39  		name      string
    40  		inputArgs map[string]string
    41  		wantArgs  map[string]buildx.NamedContext
    42  	}{
    43  		{name: "Basic conversion",
    44  			inputArgs: map[string]string{"context1": "/path/to/context1", "context2": "/path/to/context2"},
    45  			wantArgs:  map[string]buildx.NamedContext{"context1": {Path: "/path/to/context1"}, "context2": {Path: "/path/to/context2"}}},
    46  		{name: "Single entry",
    47  			inputArgs: map[string]string{"singlecontext": "/single/path"},
    48  			wantArgs:  map[string]buildx.NamedContext{"singlecontext": {Path: "/single/path"}}},
    49  		{name: "Empty path",
    50  			inputArgs: map[string]string{"singlecontext": ""},
    51  			wantArgs:  map[string]buildx.NamedContext{"singlecontext": {Path: ""}}},
    52  		{name: "Empty input map",
    53  			inputArgs: map[string]string{},
    54  			wantArgs:  map[string]buildx.NamedContext{}},
    55  	}
    56  
    57  	for _, tc := range testcases {
    58  		t.Run(tc.name, func(t *testing.T) {
    59  			var gotArgs = toNamedContexts(tc.inputArgs)
    60  			assert.Equal(t, tc.wantArgs, gotArgs)
    61  		})
    62  	}
    63  }
    64  
    65  func Test_flattenMap(t *testing.T) {
    66  	tt := []struct {
    67  		desc string
    68  		inp  map[string]interface{}
    69  		out  map[string]string
    70  		err  bool
    71  	}{
    72  		{
    73  			desc: "one pair",
    74  			inp: map[string]interface{}{
    75  				"key": "value",
    76  			},
    77  			out: map[string]string{
    78  				"key": "value",
    79  			},
    80  			err: false,
    81  		},
    82  		{
    83  			desc: "nested input",
    84  			inp: map[string]interface{}{
    85  				"key": map[string]string{
    86  					"nestedKey": "value",
    87  				},
    88  			},
    89  			out: map[string]string{
    90  				"key.nestedKey": "value",
    91  			},
    92  			err: false,
    93  		},
    94  		{
    95  			desc: "nested input",
    96  			inp: map[string]interface{}{
    97  				"key1": map[string]interface{}{
    98  					"key2": map[string]string{
    99  						"key3": "value",
   100  					},
   101  				},
   102  			},
   103  			out: map[string]string{
   104  				"key1.key2.key3": "value",
   105  			},
   106  			err: false,
   107  		},
   108  		{
   109  			desc: "multiple nested input",
   110  			inp: map[string]interface{}{
   111  				"key11": map[string]interface{}{
   112  					"key12": map[string]string{
   113  						"key13": "value1",
   114  					},
   115  				},
   116  				"key21": map[string]string{
   117  					"key22": "value2",
   118  				},
   119  			},
   120  			out: map[string]string{
   121  				"key11.key12.key13": "value1",
   122  				"key21.key22":       "value2",
   123  			},
   124  			err: false,
   125  		},
   126  		{
   127  			// CNAB represents null parameters as empty strings, so we will do the same, e.g. ARG CUSTOM_FOO=
   128  			desc: "nil is converted empty string",
   129  			inp: map[string]interface{}{
   130  				"a": nil,
   131  			},
   132  			out: map[string]string{
   133  				"a": "",
   134  			},
   135  			err: false,
   136  		},
   137  		{
   138  			desc: "int is converted to string representation",
   139  			inp: map[string]interface{}{
   140  				"a": 1,
   141  			},
   142  			out: map[string]string{
   143  				"a": "1",
   144  			},
   145  			err: false,
   146  		},
   147  		{
   148  			desc: "bool is converted to string representation",
   149  			inp: map[string]interface{}{
   150  				"a": true,
   151  			},
   152  			out: map[string]string{
   153  				"a": "true",
   154  			},
   155  			err: false,
   156  		},
   157  		{
   158  			desc: "array is converted to string representation",
   159  			inp: map[string]interface{}{
   160  				"a": []string{"beep", "boop"},
   161  			},
   162  			out: map[string]string{
   163  				"a": `["beep","boop"]`,
   164  			},
   165  			err: false,
   166  		},
   167  	}
   168  
   169  	for _, tc := range tt {
   170  		t.Run(tc.desc, func(t *testing.T) {
   171  			out, err := flattenMap(tc.inp)
   172  			if tc.err {
   173  				require.Error(t, err)
   174  				return
   175  			}
   176  			require.NoError(t, err)
   177  			assert.Equal(t, tc.out, out)
   178  		})
   179  	}
   180  }
   181  
   182  func Test_detectCustomBuildArgsUsed(t *testing.T) {
   183  	contents := `FROM scratch
   184  ARG BAR=1
   185  ARG CUSTOM_FOO=2
   186  ARG CUSTOM_FOO1_BAR=nope
   187  ARG CUSTOM_STUFF_THINGS
   188  ARG NOT_CUSTOM_ARG
   189  
   190  CMD ["echo", "stuff"]
   191  `
   192  	argNames, err := detectCustomBuildArgsUsed(contents)
   193  	require.NoError(t, err, "detectCustomBuildArgsUsed failed")
   194  
   195  	wantArgs := map[string]struct{}{
   196  		"CUSTOM_FOO":          {},
   197  		"CUSTOM_FOO1_BAR":     {},
   198  		"CUSTOM_STUFF_THINGS": {},
   199  	}
   200  	require.Equal(t, wantArgs, argNames)
   201  }
   202  
   203  // This value is exactly the max sized argument allowed
   204  //
   205  //go:embed testdata/max-arg.txt
   206  var maxArg string
   207  
   208  func TestBuilder_determineBuildArgs(t *testing.T) {
   209  	// This value goes over the limit of arg size
   210  	oversizedArg := maxArg + "oopstoobig"
   211  
   212  	ctx := context.Background()
   213  	c := config.NewTestConfig(t)
   214  	b := NewBuilder(c.Config)
   215  	m := &manifest.Manifest{
   216  		Custom: map[string]interface{}{
   217  			// these can cause a problem when used as a build arg in the Dockerfile
   218  			"BIG_LABEL":         oversizedArg,
   219  			"ANOTHER_BIG_LABEL": oversizedArg,
   220  		},
   221  	}
   222  	// First we use the standard template, that does not use any custom build arguments
   223  	c.TestContext.AddTestFileFromRoot("pkg/templates/templates/create/template.buildkit.Dockerfile", "/.cnab/Dockerfile")
   224  
   225  	// Try manually passing too big of a --build-arg, this should fail
   226  	opts := build.BuildImageOptions{
   227  		BuildArgs: []string{"BIG_BUILD_ARG=" + oversizedArg},
   228  	}
   229  	_, err := b.determineBuildArgs(ctx, m, opts)
   230  	assert.ErrorContains(t, err, "BIG_BUILD_ARG is longer than the max")
   231  
   232  	// Make the --build-arg the max length, so it passes
   233  	// Try making a too big custom value in porter.yaml and using it in the Dockerfile so that it still fails
   234  	opts.BuildArgs = []string{"BIG_BUILD_ARG=" + maxArg}
   235  	c.TestContext.AddTestFile("testdata/custom-build-arg.Dockerfile", "/.cnab/Dockerfile")
   236  	_, err = b.determineBuildArgs(ctx, m, opts)
   237  	require.ErrorContains(t, err, "CUSTOM_BIG_LABEL is longer than the max")
   238  
   239  	// Get everything to pass by making all big args the max length
   240  	m.Custom["BIG_LABEL"] = maxArg
   241  	args, err := b.determineBuildArgs(ctx, m, opts)
   242  	require.NoError(t, err, "determineBuildArgs should pass now that all args are at the max length")
   243  	wantArgs := map[string]string{
   244  		"BUNDLE_DIR":       "/cnab/app",
   245  		"BIG_BUILD_ARG":    maxArg,
   246  		"CUSTOM_BIG_LABEL": maxArg}
   247  	require.Equal(t, wantArgs, args, "incorrect arguments returned")
   248  }