get.porter.sh/porter@v1.3.0/pkg/build/dockerfile-generator_test.go (about) 1 package build 2 3 import ( 4 "context" 5 "path/filepath" 6 "strings" 7 "testing" 8 9 "get.porter.sh/porter/pkg/config" 10 "get.porter.sh/porter/pkg/experimental" 11 "get.porter.sh/porter/pkg/manifest" 12 "get.porter.sh/porter/pkg/mixin" 13 "get.porter.sh/porter/pkg/templates" 14 "get.porter.sh/porter/pkg/test" 15 "get.porter.sh/porter/tests" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func TestPorter_buildDockerfile(t *testing.T) { 21 t.Parallel() 22 23 c := config.NewTestConfig(t) 24 c.Data.BuildDriver = config.BuildDriverBuildkit 25 tmpl := templates.NewTemplates(c.Config) 26 configTpl, err := tmpl.GetManifest() 27 require.Nil(t, err) 28 require.NoError(t, c.TestContext.AddTestFileContents(configTpl, config.Name)) 29 30 m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name) 31 require.NoError(t, err, "could not load manifest") 32 33 // Add another mixin to ensure we are consistently sorting the results 34 m.Mixins = append(m.Mixins, manifest.MixinDeclaration{Name: "testmixin"}) 35 36 mp := mixin.NewTestMixinProvider() 37 g := NewDockerfileGenerator(c.Config, m, tmpl, mp) 38 gotlines, err := g.buildDockerfile(context.Background()) 39 require.NoError(t, err) 40 gotDockerfile := strings.Join(gotlines, "\n") 41 42 wantDockerfilePath := "testdata/buildkit.Dockerfile" 43 test.CompareGoldenFile(t, wantDockerfilePath, gotDockerfile) 44 } 45 46 func TestPorter_buildCustomDockerfile(t *testing.T) { 47 t.Parallel() 48 49 t.Run("build from custom docker without supplying ARG BUNDLE_DIR", func(t *testing.T) { 50 t.Parallel() 51 52 c := config.NewTestConfig(t) 53 tmpl := templates.NewTemplates(c.Config) 54 configTpl, err := tmpl.GetManifest() 55 require.Nil(t, err) 56 require.NoError(t, c.TestContext.AddTestFileContents(configTpl, config.Name)) 57 58 m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name) 59 require.NoError(t, err, "could not load manifest") 60 61 // Use a custom dockerfile template 62 m.Dockerfile = "Dockerfile.template" 63 customFrom := `FROM ubuntu:latest 64 COPY mybin /cnab/app/ 65 66 ` 67 err = c.TestContext.AddTestFileContents([]byte(customFrom), "Dockerfile.template") 68 require.NoError(t, err) 69 70 mp := mixin.NewTestMixinProvider() 71 g := NewDockerfileGenerator(c.Config, m, tmpl, mp) 72 gotlines, err := g.buildDockerfile(context.Background()) 73 74 // We should inject initialization lines even when they didn't include the token 75 require.NoError(t, err) 76 test.CompareGoldenFile(t, "testdata/missing-args-expected-output.Dockerfile", strings.Join(gotlines, "\n")) 77 }) 78 79 t.Run("build from custom docker with PORTER_INIT supplied", func(t *testing.T) { 80 t.Parallel() 81 82 c := config.NewTestConfig(t) 83 tmpl := templates.NewTemplates(c.Config) 84 configTpl, err := tmpl.GetManifest() 85 require.Nil(t, err) 86 require.NoError(t, c.TestContext.AddTestFileContents(configTpl, config.Name)) 87 88 m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name) 89 require.NoError(t, err, "could not load manifest") 90 91 // Use a custom dockerfile template 92 m.Dockerfile = "Dockerfile.template" 93 customFrom := `FROM ubuntu:latest 94 # stuff 95 # PORTER_INIT 96 COPY mybin /cnab/app/ 97 98 ` 99 err = c.TestContext.AddTestFileContents([]byte(customFrom), "Dockerfile.template") 100 require.NoError(t, err) 101 102 mp := mixin.NewTestMixinProvider() 103 g := NewDockerfileGenerator(c.Config, m, tmpl, mp) 104 gotlines, err := g.buildDockerfile(context.Background()) 105 106 require.NoError(t, err) 107 test.CompareGoldenFile(t, "testdata/custom-dockerfile-expected-output.Dockerfile", strings.Join(gotlines, "\n")) 108 }) 109 110 t.Run("build from custom docker without PORTER_INIT supplied", func(t *testing.T) { 111 t.Parallel() 112 113 c := config.NewTestConfig(t) 114 tmpl := templates.NewTemplates(c.Config) 115 configTpl, err := tmpl.GetManifest() 116 require.Nil(t, err) 117 require.NoError(t, c.TestContext.AddTestFileContents(configTpl, config.Name)) 118 119 m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name) 120 require.NoError(t, err, "could not load manifest") 121 122 // Use a custom dockerfile template 123 m.Dockerfile = "Dockerfile.template" 124 customFrom := `FROM ubuntu:latest 125 # stuff 126 COPY mybin /cnab/app/ 127 128 ` 129 err = c.TestContext.AddTestFileContents([]byte(customFrom), "Dockerfile.template") 130 require.NoError(t, err) 131 132 mp := mixin.NewTestMixinProvider() 133 g := NewDockerfileGenerator(c.Config, m, tmpl, mp) 134 gotlines, err := g.buildDockerfile(context.Background()) 135 136 require.NoError(t, err) 137 test.CompareGoldenFile(t, "testdata/custom-dockerfile-without-init-expected-output.Dockerfile", strings.Join(gotlines, "\n")) 138 }) 139 } 140 141 func TestPorter_generateDockerfile(t *testing.T) { 142 t.Parallel() 143 144 ctx := context.Background() 145 c := config.NewTestConfig(t) 146 defer c.Close() 147 148 // Start a span so we can capture the output 149 ctx, log := c.StartRootSpan(ctx, t.Name()) 150 defer log.Close() 151 152 tmpl := templates.NewTemplates(c.Config) 153 configTpl, err := tmpl.GetManifest() 154 require.Nil(t, err) 155 require.NoError(t, c.TestContext.AddTestFileContents(configTpl, config.Name)) 156 157 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 158 require.NoError(t, err, "could not load manifest") 159 160 // Add another mixin to ensure we are consistently sorting the results 161 m.Mixins = append(m.Mixins, manifest.MixinDeclaration{Name: "testmixin"}) 162 163 mp := mixin.NewTestMixinProvider() 164 g := NewDockerfileGenerator(c.Config, m, tmpl, mp) 165 err = g.GenerateDockerFile(ctx) 166 require.NoError(t, err) 167 168 wantDockerfilePath := ".cnab/Dockerfile" 169 gotDockerfile, err := c.FileSystem.ReadFile(wantDockerfilePath) 170 require.NoError(t, err) 171 172 // Verify that we logged the dockerfile contents 173 tests.RequireOutputContains(t, c.TestContext.GetOutput(), string(gotDockerfile), "expected the dockerfile to be printed to the logs") 174 test.CompareGoldenFile(t, "testdata/buildkit.Dockerfile", string(gotDockerfile)) 175 176 // Verify that we didn't generate a Dockerfile at the root of the bundle dir 177 oldDockerfilePathExists, _ := c.FileSystem.Exists("Dockerfile") 178 assert.False(t, oldDockerfilePathExists, "expected the Dockerfile to be placed only at .cnab/Dockerfile, not at the root of the bundle directory") 179 } 180 181 func TestPorter_prepareDockerFilesystem(t *testing.T) { 182 t.Parallel() 183 184 c := config.NewTestConfig(t) 185 tmpl := templates.NewTemplates(c.Config) 186 configTpl, err := tmpl.GetManifest() 187 require.Nil(t, err) 188 require.NoError(t, c.TestContext.AddTestFileContents(configTpl, config.Name)) 189 190 m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name) 191 require.NoError(t, err, "could not load manifest") 192 193 mp := mixin.NewTestMixinProvider() 194 g := NewDockerfileGenerator(c.Config, m, tmpl, mp) 195 err = g.PrepareFilesystem() 196 require.NoError(t, err) 197 198 wantRunscript := LOCAL_RUN 199 runscriptExists, err := c.FileSystem.Exists(wantRunscript) 200 require.NoError(t, err) 201 assert.True(t, runscriptExists, "The run script wasn't copied into %s", wantRunscript) 202 203 wantPorterRuntime := filepath.Join(LOCAL_APP, "runtimes", "porter-runtime") 204 porterMixinExists, err := c.FileSystem.Exists(wantPorterRuntime) 205 require.NoError(t, err) 206 assert.True(t, porterMixinExists, "The porter-runtime wasn't copied into %s", wantPorterRuntime) 207 208 wantExecMixin := filepath.Join(LOCAL_APP, "mixins", "exec", "runtimes", "exec-runtime") 209 execMixinExists, err := c.FileSystem.Exists(wantExecMixin) 210 require.NoError(t, err) 211 assert.True(t, execMixinExists, "The exec-runtime mixin wasn't copied into %s", wantExecMixin) 212 } 213 214 func TestPorter_appendBuildInstructionsIfMixinTokenIsNotPresent(t *testing.T) { 215 t.Parallel() 216 217 c := config.NewTestConfig(t) 218 tmpl := templates.NewTemplates(c.Config) 219 configTpl, err := tmpl.GetManifest() 220 require.Nil(t, err) 221 require.NoError(t, c.TestContext.AddTestFileContents(configTpl, config.Name)) 222 223 m, err := manifest.LoadManifestFrom(context.Background(), c.Config, config.Name) 224 require.NoError(t, err, "could not load manifest") 225 226 // Use a custom dockerfile template 227 m.Dockerfile = "Dockerfile.template" 228 customFrom := `FROM ubuntu:light 229 ARG BUNDLE_DIR 230 COPY mybin /cnab/app/ 231 ` 232 err = c.TestContext.AddTestFileContents([]byte(customFrom), "Dockerfile.template") 233 require.NoError(t, err) 234 235 mp := mixin.NewTestMixinProvider() 236 g := NewDockerfileGenerator(c.Config, m, tmpl, mp) 237 238 gotlines, err := g.buildDockerfile(context.Background()) 239 require.NoError(t, err) 240 241 test.CompareGoldenFile(t, "testdata/missing-mixins-token-expected-output.Dockerfile", strings.Join(gotlines, "\n")) 242 } 243 244 func TestPorter_buildMixinsSection_mixinErr(t *testing.T) { 245 t.Parallel() 246 247 ctx := context.Background() 248 c := config.NewTestConfig(t) 249 tmpl := templates.NewTemplates(c.Config) 250 configTpl, err := tmpl.GetManifest() 251 require.Nil(t, err) 252 require.NoError(t, c.TestContext.AddTestFileContents(configTpl, config.Name)) 253 254 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 255 require.NoError(t, err, "could not load manifest") 256 257 mp := mixin.NewTestMixinProvider() 258 mp.ReturnBuildError = true 259 g := NewDockerfileGenerator(c.Config, m, tmpl, mp) 260 _, err = g.buildMixinsSection(ctx) 261 require.EqualError(t, err, "1 error occurred:\n\t* error encountered from mixin \"exec\": encountered build error\n\n") 262 } 263 264 func TestPorter_GenerateDockerfile_WithExperimentalFlagFullControlDockerfile(t *testing.T) { 265 t.Parallel() 266 267 ctx := context.Background() 268 c := config.NewTestConfig(t) 269 270 // Enable the experimental feature 271 c.SetExperimentalFlags(experimental.FlagFullControlDockerfile) 272 273 defer c.Close() 274 275 // Start a span so we can capture the output 276 ctx, log := c.StartRootSpan(ctx, t.Name()) 277 defer log.Close() 278 279 tmpl := templates.NewTemplates(c.Config) 280 configTpl, err := tmpl.GetManifest() 281 require.Nil(t, err) 282 require.NoError(t, c.TestContext.AddTestFileContents(configTpl, config.Name)) 283 284 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 285 require.NoError(t, err, "could not load manifest") 286 287 // Use a custom dockerfile template 288 m.Dockerfile = "Dockerfile.template" 289 customFrom := `FROM ubuntu:latest 290 # stuff 291 RUN random-command 292 # PORTER_INIT 293 COPY mybin /cnab/app/ 294 # No attempts are made to validate the contents 295 # Nor does it modify the contents in any way` 296 require.NoError(t, c.TestContext.AddTestFileContents([]byte(customFrom), "Dockerfile.template")) 297 298 mp := mixin.NewTestMixinProvider() 299 g := NewDockerfileGenerator(c.Config, m, tmpl, mp) 300 err = g.GenerateDockerFile(ctx) 301 require.NoError(t, err) 302 303 wantDockerfilePath := ".cnab/Dockerfile" 304 gotDockerfile, err := c.FileSystem.ReadFile(wantDockerfilePath) 305 require.NoError(t, err) 306 307 // Verify that we logged the dockerfile contents 308 tests.RequireOutputContains(t, c.TestContext.GetOutput(), string(gotDockerfile), "expected the dockerfile to be printed to the logs") 309 assert.Equal(t, customFrom, string(gotDockerfile)) 310 } 311 312 func TestPorter_GenerateDockerfile_WithExperimentalFlagFullControlDockerfile_RequiresDockerfileSpecified(t *testing.T) { 313 t.Parallel() 314 315 ctx := context.Background() 316 c := config.NewTestConfig(t) 317 318 // Enable the experimental feature 319 c.SetExperimentalFlags(experimental.FlagFullControlDockerfile) 320 321 defer c.Close() 322 323 // Start a span so we can capture the output 324 ctx, log := c.StartRootSpan(ctx, t.Name()) 325 defer log.Close() 326 327 tmpl := templates.NewTemplates(c.Config) 328 configTpl, err := tmpl.GetManifest() 329 require.Nil(t, err) 330 require.NoError(t, c.TestContext.AddTestFileContents(configTpl, config.Name)) 331 332 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 333 require.NoError(t, err, "could not load manifest") 334 335 mp := mixin.NewTestMixinProvider() 336 g := NewDockerfileGenerator(c.Config, m, tmpl, mp) 337 err = g.GenerateDockerFile(ctx) 338 339 require.EqualError(t, err, "error reading the Dockerfile: no Dockerfile specified in the manifest") 340 } 341 342 func TestPorter_GenerateDockerfile_WithExperimentalFlagFullControlDockerfile_DockerfileMustExist(t *testing.T) { 343 t.Parallel() 344 345 ctx := context.Background() 346 c := config.NewTestConfig(t) 347 348 // Enable the experimental feature 349 c.SetExperimentalFlags(experimental.FlagFullControlDockerfile) 350 351 defer c.Close() 352 353 // Start a span so we can capture the output 354 ctx, log := c.StartRootSpan(ctx, t.Name()) 355 defer log.Close() 356 357 tmpl := templates.NewTemplates(c.Config) 358 configTpl, err := tmpl.GetManifest() 359 require.Nil(t, err) 360 require.NoError(t, c.TestContext.AddTestFileContents(configTpl, config.Name)) 361 362 m, err := manifest.LoadManifestFrom(ctx, c.Config, config.Name) 363 require.NoError(t, err, "could not load manifest") 364 365 m.Dockerfile = "this-file-does-not-exist.dockerfile" 366 367 mp := mixin.NewTestMixinProvider() 368 g := NewDockerfileGenerator(c.Config, m, tmpl, mp) 369 err = g.GenerateDockerFile(ctx) 370 371 require.EqualError(t, err, "error reading the Dockerfile: the Dockerfile specified in the manifest doesn't exist: \"this-file-does-not-exist.dockerfile\"") 372 }