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  }