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 }