github.com/docker/compose-on-kubernetes@v0.5.0/internal/convert/pod_test.go (about)

     1  package convert
     2  
     3  import (
     4  	"fmt"
     5  	"runtime"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/docker/compose-on-kubernetes/api/compose/latest"
    10  	"github.com/docker/compose-on-kubernetes/internal/stackresources"
    11  	. "github.com/docker/compose-on-kubernetes/internal/test/builders"
    12  	"github.com/stretchr/testify/assert"
    13  	apiv1 "k8s.io/api/core/v1"
    14  	"k8s.io/apimachinery/pkg/api/resource"
    15  )
    16  
    17  func podTemplate(t *testing.T, stack *latest.Stack) apiv1.PodTemplateSpec {
    18  	res, err := podTemplateWithError(stack)
    19  	assert.NoError(t, err)
    20  	return res
    21  }
    22  
    23  func podTemplateWithError(stack *latest.Stack) (apiv1.PodTemplateSpec, error) {
    24  	s, err := StackToStack(*stack, loadBalancerServiceStrategy{}, stackresources.EmptyStackState)
    25  	if err != nil {
    26  		return apiv1.PodTemplateSpec{}, err
    27  	}
    28  	for _, r := range s.Deployments {
    29  		return r.Spec.Template, nil
    30  	}
    31  	for _, r := range s.Daemonsets {
    32  		return r.Spec.Template, nil
    33  	}
    34  	for _, r := range s.Statefulsets {
    35  		return r.Spec.Template, nil
    36  	}
    37  	return apiv1.PodTemplateSpec{}, nil
    38  }
    39  
    40  func TestToPodWithDockerSocket(t *testing.T) {
    41  	if runtime.GOOS == "windows" {
    42  		t.Skip("on windows, source path validation is broken (and actually, source validation for windows workload is broken too). Skip it for now, as we don't support it yet")
    43  		return
    44  	}
    45  	podTemplate := podTemplate(t, Stack("demo",
    46  		WithService("redis",
    47  			Image("redis:alpine"),
    48  			WithVolume(
    49  				Source("/var/run/docker.sock"),
    50  				Target("/var/run/docker.sock"),
    51  				Mount,
    52  			),
    53  		),
    54  	))
    55  
    56  	expectedVolume := apiv1.Volume{
    57  		Name: "mount-0",
    58  		VolumeSource: apiv1.VolumeSource{
    59  			HostPath: &apiv1.HostPathVolumeSource{
    60  				Path: "/var/run",
    61  			},
    62  		},
    63  	}
    64  
    65  	expectedMount := apiv1.VolumeMount{
    66  		Name:      "mount-0",
    67  		MountPath: "/var/run/docker.sock",
    68  		SubPath:   "docker.sock",
    69  	}
    70  
    71  	assert.Len(t, podTemplate.Spec.Volumes, 1)
    72  	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
    73  	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
    74  	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
    75  }
    76  
    77  func TestToPodWithFunkyCommand(t *testing.T) {
    78  	podTemplate := podTemplate(t, Stack("demo",
    79  		WithService("redis",
    80  			Image("basi/node-exporter"),
    81  			Command("-collector.procfs", "/host/proc", "-collector.sysfs", "/host/sys"),
    82  		),
    83  	))
    84  
    85  	expectedArgs := []string{
    86  		`-collector.procfs`,
    87  		`/host/proc`, // ?
    88  		`-collector.sysfs`,
    89  		`/host/sys`, // ?
    90  	}
    91  	assert.Equal(t, expectedArgs, podTemplate.Spec.Containers[0].Args)
    92  }
    93  
    94  func TestToPodWithGlobalVolume(t *testing.T) {
    95  	podTemplate := podTemplate(t, Stack("demo",
    96  		WithService("db",
    97  			Image("postgres:9.4"),
    98  			WithVolume(
    99  				Source("dbdata"),
   100  				Target("/var/lib/postgresql/data"),
   101  				Volume,
   102  			),
   103  		),
   104  	))
   105  
   106  	expectedMount := apiv1.VolumeMount{
   107  		Name:      "dbdata",
   108  		MountPath: "/var/lib/postgresql/data",
   109  	}
   110  	assert.Len(t, podTemplate.Spec.Volumes, 0)
   111  	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
   112  	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
   113  }
   114  
   115  func TestToPodWithResources(t *testing.T) {
   116  	podTemplate := podTemplate(t, Stack("demo",
   117  		WithService("db",
   118  			Image("postgres:9.4"),
   119  			Deploy(Resources(
   120  				Limits(CPUs("0.001"), Memory(50*1024*1024)),
   121  				Reservations(CPUs("0.0001"), Memory(20*1024*1024)),
   122  			)),
   123  		),
   124  	))
   125  
   126  	expectedResourceRequirements := apiv1.ResourceRequirements{
   127  		Limits: map[apiv1.ResourceName]resource.Quantity{
   128  			apiv1.ResourceCPU:    resource.MustParse("0.001"),
   129  			apiv1.ResourceMemory: resource.MustParse(fmt.Sprintf("%d", 50*1024*1024)),
   130  		},
   131  		Requests: map[apiv1.ResourceName]resource.Quantity{
   132  			apiv1.ResourceCPU:    resource.MustParse("0.0001"),
   133  			apiv1.ResourceMemory: resource.MustParse(fmt.Sprintf("%d", 20*1024*1024)),
   134  		},
   135  	}
   136  	assert.Equal(t, expectedResourceRequirements, podTemplate.Spec.Containers[0].Resources)
   137  }
   138  
   139  func TestToPodWithCapabilities(t *testing.T) {
   140  	podTemplate := podTemplate(t, Stack("demo",
   141  		WithService("redis",
   142  			Image("redis:alpine"),
   143  			WithCapAdd("ALL"),
   144  			WithCapDrop("NET_ADMIN", "SYS_ADMIN"),
   145  		),
   146  	))
   147  
   148  	expectedSecurityContext := &apiv1.SecurityContext{
   149  		Capabilities: &apiv1.Capabilities{
   150  			Add:  []apiv1.Capability{"ALL"},
   151  			Drop: []apiv1.Capability{"NET_ADMIN", "SYS_ADMIN"},
   152  		},
   153  	}
   154  
   155  	assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext)
   156  }
   157  
   158  func TestToPodWithReadOnly(t *testing.T) {
   159  	podTemplate := podTemplate(t, Stack("demo",
   160  		WithService("redis",
   161  			Image("redis:alpine"),
   162  			ReadOnly,
   163  		),
   164  	))
   165  
   166  	yes := true
   167  	expectedSecurityContext := &apiv1.SecurityContext{
   168  		ReadOnlyRootFilesystem: &yes,
   169  	}
   170  	assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext)
   171  }
   172  
   173  func TestToPodWithPrivileged(t *testing.T) {
   174  	podTemplate := podTemplate(t, Stack("demo",
   175  		WithService("redis",
   176  			Image("redis:alpine"),
   177  			Privileged,
   178  		),
   179  	))
   180  
   181  	yes := true
   182  	expectedSecurityContext := &apiv1.SecurityContext{
   183  		Privileged: &yes,
   184  	}
   185  	assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext)
   186  }
   187  
   188  func strptr(s string) *string {
   189  	return &s
   190  }
   191  
   192  func TestToPodWithEnvNilShouldErrorOut(t *testing.T) {
   193  	stack := Stack("demo",
   194  		WithService("redis",
   195  			Image("redis:alpine"),
   196  			WithEnvironment("SESSION_SECRET", nil),
   197  		),
   198  	)
   199  	_, err := StackToStack(*stack, loadBalancerServiceStrategy{}, stackresources.EmptyStackState)
   200  	assert.Error(t, err)
   201  }
   202  
   203  func TestToPodWithEnv(t *testing.T) {
   204  	podTemplate := podTemplate(t, Stack("demo",
   205  		WithService("redis",
   206  			Image("redis:alpine"),
   207  			WithEnvironment("RACK_ENV", strptr("development")),
   208  			WithEnvironment("SHOW", strptr("true")),
   209  		),
   210  	))
   211  
   212  	expectedEnv := []apiv1.EnvVar{
   213  		{
   214  			Name:  "RACK_ENV",
   215  			Value: "development",
   216  		},
   217  		{
   218  			Name:  "SHOW",
   219  			Value: "true",
   220  		},
   221  	}
   222  
   223  	assert.Equal(t, expectedEnv, podTemplate.Spec.Containers[0].Env)
   224  }
   225  
   226  func TestToPodWithVolume(t *testing.T) {
   227  	if runtime.GOOS == "windows" {
   228  		t.Skip("on windows, source path validation is broken (and actually, source validation for windows workload is broken too). Skip it for now, as we don't support it yet")
   229  		return
   230  	}
   231  	podTemplate := podTemplate(t, Stack("demo",
   232  		WithService("nginx",
   233  			Image("nginx"),
   234  			WithVolume(Source("/ignore"), Target("/ignore"), Mount),
   235  			WithVolume(Source("/opt/data"), Target("/var/lib/mysql"), VolumeReadOnly, Mount),
   236  		),
   237  	))
   238  
   239  	assert.Len(t, podTemplate.Spec.Volumes, 2)
   240  	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 2)
   241  }
   242  
   243  func TestToPodWithRelativeVolumes(t *testing.T) {
   244  	if runtime.GOOS == "windows" {
   245  		t.Skip("on windows, source path validation is broken (and actually, source validation for windows workload is broken too). Skip it for now, as we don't support it yet")
   246  		return
   247  	}
   248  	stack := Stack("demo",
   249  		WithService("nginx",
   250  			Image("nginx"),
   251  			WithVolume(Source("./fail"), Target("/ignore"), Mount),
   252  		))
   253  	_, err := StackToStack(*stack, loadBalancerServiceStrategy{}, stackresources.EmptyStackState)
   254  	assert.Error(t, err)
   255  }
   256  
   257  func TestToPodWithHealthCheck(t *testing.T) {
   258  	podTemplate := podTemplate(t, Stack("demo",
   259  		WithService("nginx",
   260  			Image("nginx"),
   261  			Healthcheck(
   262  				Test("CMD", "curl", "-f", "http://localhost"),
   263  				Interval(90*time.Second),
   264  				Timeout(10*time.Second),
   265  				Retries(3),
   266  			),
   267  		),
   268  	))
   269  	expectedLivenessProbe := &apiv1.Probe{
   270  		TimeoutSeconds:   10,
   271  		PeriodSeconds:    90,
   272  		FailureThreshold: 3,
   273  		Handler: apiv1.Handler{
   274  			Exec: &apiv1.ExecAction{
   275  				Command: []string{"curl", "-f", "http://localhost"},
   276  			},
   277  		},
   278  	}
   279  
   280  	assert.Equal(t, expectedLivenessProbe, podTemplate.Spec.Containers[0].LivenessProbe)
   281  }
   282  
   283  func TestToPodWithShellHealthCheck(t *testing.T) {
   284  	podTemplate := podTemplate(t, Stack("demo",
   285  		WithService("nginx",
   286  			Image("nginx"),
   287  			Healthcheck(
   288  				Test("CMD-SHELL", "curl -f http://localhost"),
   289  			),
   290  		),
   291  	))
   292  
   293  	expectedLivenessProbe := &apiv1.Probe{
   294  		TimeoutSeconds:   1,
   295  		PeriodSeconds:    1,
   296  		FailureThreshold: 3,
   297  		Handler: apiv1.Handler{
   298  			Exec: &apiv1.ExecAction{
   299  				Command: []string{"sh", "-c", "curl -f http://localhost"},
   300  			},
   301  		},
   302  	}
   303  
   304  	assert.Equal(t, expectedLivenessProbe, podTemplate.Spec.Containers[0].LivenessProbe)
   305  }
   306  
   307  func TestToPodWithHealthCheckEmptyCommand(t *testing.T) {
   308  	podTemplate := podTemplate(t, Stack("demo",
   309  		WithService("nginx",
   310  			Image("nginx"),
   311  			Healthcheck(Test()),
   312  		),
   313  	))
   314  
   315  	assert.Nil(t, podTemplate.Spec.Containers[0].LivenessProbe)
   316  }
   317  
   318  func TestToPodWithTargetlessExternalSecret(t *testing.T) {
   319  	podTemplate := podTemplate(t, Stack("demo",
   320  		WithService("nginx",
   321  			Image("nginx"),
   322  			WithSecret(
   323  				SecretSource("my_secret"),
   324  			),
   325  		),
   326  	))
   327  
   328  	expectedVolume := apiv1.Volume{
   329  		Name: "secret-0",
   330  		VolumeSource: apiv1.VolumeSource{
   331  			Secret: &apiv1.SecretVolumeSource{
   332  				SecretName: "my_secret",
   333  				Items: []apiv1.KeyToPath{
   334  					{
   335  						Key:  "file", // TODO: This is the key we assume external secrets use
   336  						Path: "secret-0",
   337  					},
   338  				},
   339  			},
   340  		},
   341  	}
   342  
   343  	expectedMount := apiv1.VolumeMount{
   344  		Name:      "secret-0",
   345  		ReadOnly:  true,
   346  		MountPath: "/run/secrets/my_secret",
   347  		SubPath:   "secret-0",
   348  	}
   349  
   350  	assert.Len(t, podTemplate.Spec.Volumes, 1)
   351  	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
   352  	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
   353  	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
   354  }
   355  
   356  func TestToPodWithExternalSecret(t *testing.T) {
   357  	podTemplate := podTemplate(t, Stack("demo",
   358  		WithService("nginx",
   359  			Image("nginx"),
   360  			WithSecret(
   361  				SecretSource("my_secret"),
   362  				SecretTarget("nginx_secret"),
   363  			),
   364  		),
   365  	))
   366  
   367  	expectedVolume := apiv1.Volume{
   368  		Name: "secret-0",
   369  		VolumeSource: apiv1.VolumeSource{
   370  			Secret: &apiv1.SecretVolumeSource{
   371  				SecretName: "my_secret",
   372  				Items: []apiv1.KeyToPath{
   373  					{
   374  						Key:  "file", // TODO: This is the key we assume external secrets use
   375  						Path: "secret-0",
   376  					},
   377  				},
   378  			},
   379  		},
   380  	}
   381  
   382  	expectedMount := apiv1.VolumeMount{
   383  		Name:      "secret-0",
   384  		ReadOnly:  true,
   385  		MountPath: "/run/secrets/nginx_secret",
   386  		SubPath:   "secret-0",
   387  	}
   388  
   389  	assert.Len(t, podTemplate.Spec.Volumes, 1)
   390  	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
   391  	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
   392  	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
   393  }
   394  
   395  func TestToPodWithFileBasedSecret(t *testing.T) {
   396  	podTemplate := podTemplate(t, Stack("demo",
   397  		WithService("nginx",
   398  			Image("nginx"),
   399  			WithSecret(
   400  				SecretSource("my_secret"),
   401  			),
   402  		),
   403  		WithSecretConfig("my_secret", SecretFile("./secret.txt")),
   404  	))
   405  
   406  	expectedVolume := apiv1.Volume{
   407  		Name: "secret-0",
   408  		VolumeSource: apiv1.VolumeSource{
   409  			Secret: &apiv1.SecretVolumeSource{
   410  				SecretName: "my_secret",
   411  				Items: []apiv1.KeyToPath{
   412  					{
   413  						Key:  "secret.txt",
   414  						Path: "secret-0",
   415  					},
   416  				},
   417  			},
   418  		},
   419  	}
   420  
   421  	expectedMount := apiv1.VolumeMount{
   422  		Name:      "secret-0",
   423  		ReadOnly:  true,
   424  		MountPath: "/run/secrets/my_secret",
   425  		SubPath:   "secret-0",
   426  	}
   427  
   428  	assert.Len(t, podTemplate.Spec.Volumes, 1)
   429  	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
   430  	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
   431  	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
   432  }
   433  
   434  func TestToPodWithTwoFileBasedSecrets(t *testing.T) {
   435  	podTemplate := podTemplate(t, Stack("demo",
   436  		WithService("nginx",
   437  			Image("nginx"),
   438  			WithSecret(
   439  				SecretSource("my_secret1"),
   440  			),
   441  			WithSecret(
   442  				SecretSource("my_secret2"),
   443  				SecretTarget("secret2"),
   444  			),
   445  		),
   446  		WithSecretConfig("my_secret1", SecretFile("./secret1.txt")),
   447  		WithSecretConfig("my_secret2", SecretFile("./secret2.txt")),
   448  	))
   449  
   450  	expectedVolumes := []apiv1.Volume{
   451  		{
   452  			Name: "secret-0",
   453  			VolumeSource: apiv1.VolumeSource{
   454  				Secret: &apiv1.SecretVolumeSource{
   455  					SecretName: "my_secret1",
   456  					Items: []apiv1.KeyToPath{
   457  						{
   458  							Key:  "secret1.txt",
   459  							Path: "secret-0",
   460  						},
   461  					},
   462  				},
   463  			},
   464  		},
   465  		{
   466  			Name: "secret-1",
   467  			VolumeSource: apiv1.VolumeSource{
   468  				Secret: &apiv1.SecretVolumeSource{
   469  					SecretName: "my_secret2",
   470  					Items: []apiv1.KeyToPath{
   471  						{
   472  							Key:  "secret2.txt",
   473  							Path: "secret-1",
   474  						},
   475  					},
   476  				},
   477  			},
   478  		},
   479  	}
   480  
   481  	expectedMounts := []apiv1.VolumeMount{
   482  		{
   483  			Name:      "secret-0",
   484  			ReadOnly:  true,
   485  			MountPath: "/run/secrets/my_secret1",
   486  			SubPath:   "secret-0",
   487  		},
   488  		{
   489  			Name:      "secret-1",
   490  			ReadOnly:  true,
   491  			MountPath: "/run/secrets/secret2",
   492  			SubPath:   "secret-1",
   493  		},
   494  	}
   495  
   496  	assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes)
   497  	assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts)
   498  }
   499  
   500  func TestToPodWithTerminationGracePeriod(t *testing.T) {
   501  	podTemplate := podTemplate(t, Stack("demo",
   502  		WithService("redis",
   503  			Image("redis:alpine"),
   504  			StopGracePeriod(100*time.Second),
   505  		),
   506  	))
   507  
   508  	expected := int64(100)
   509  	assert.Equal(t, &expected, podTemplate.Spec.TerminationGracePeriodSeconds)
   510  }
   511  
   512  func TestToPodWithTmpfs(t *testing.T) {
   513  	podTemplate := podTemplate(t, Stack("demo",
   514  		WithService("redis",
   515  			Image("redis:alpine"),
   516  			WithTmpFS("/tmp"),
   517  		),
   518  	))
   519  
   520  	expectedVolume := apiv1.Volume{
   521  		Name: "tmp-0",
   522  		VolumeSource: apiv1.VolumeSource{
   523  			EmptyDir: &apiv1.EmptyDirVolumeSource{
   524  				Medium: "Memory",
   525  			},
   526  		},
   527  	}
   528  
   529  	expectedMount := apiv1.VolumeMount{
   530  		Name:      "tmp-0",
   531  		MountPath: "/tmp",
   532  	}
   533  
   534  	assert.Len(t, podTemplate.Spec.Volumes, 1)
   535  	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
   536  	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
   537  	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
   538  }
   539  
   540  func TestToPodWithNumericalUser(t *testing.T) {
   541  	podTemplate := podTemplate(t, Stack("demo",
   542  		WithService("redis",
   543  			Image("redis:alpine"),
   544  			User(1000),
   545  		),
   546  	))
   547  
   548  	userID := int64(1000)
   549  
   550  	expectedSecurityContext := &apiv1.SecurityContext{
   551  		RunAsUser: &userID,
   552  	}
   553  
   554  	assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext)
   555  }
   556  
   557  func TestToPodWithGitVolume(t *testing.T) {
   558  	podTemplate := podTemplate(t, Stack("demo",
   559  		WithService("redis",
   560  			Image("redis:alpine"),
   561  			WithVolume(
   562  				Source("git@github.com:moby/moby.git"),
   563  				Target("/sources"),
   564  				Mount,
   565  			),
   566  		),
   567  	))
   568  
   569  	expectedVolume := apiv1.Volume{
   570  		Name: "mount-0",
   571  		VolumeSource: apiv1.VolumeSource{
   572  			GitRepo: &apiv1.GitRepoVolumeSource{
   573  				Repository: "git@github.com:moby/moby.git",
   574  			},
   575  		},
   576  	}
   577  
   578  	expectedMount := apiv1.VolumeMount{
   579  		Name:      "mount-0",
   580  		ReadOnly:  false,
   581  		MountPath: "/sources",
   582  	}
   583  
   584  	assert.Len(t, podTemplate.Spec.Volumes, 1)
   585  	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
   586  	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
   587  	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
   588  }
   589  
   590  func TestToPodWithFileBasedConfig(t *testing.T) {
   591  	podTemplate := podTemplate(t, Stack("demo",
   592  		WithService("nginx",
   593  			Image("nginx"),
   594  			WithConfig(
   595  				ConfigSource("my_config"),
   596  				ConfigTarget("/usr/share/nginx/html/index.html"),
   597  				ConfigUID("103"),
   598  				ConfigGID("103"),
   599  				ConfigMode(0440),
   600  			),
   601  		),
   602  		WithConfigObjConfig("my_config", ConfigFile("./file.html")),
   603  	))
   604  
   605  	mode := int32(0440)
   606  
   607  	expectedVolume := apiv1.Volume{
   608  		Name: "config-0",
   609  		VolumeSource: apiv1.VolumeSource{
   610  			ConfigMap: &apiv1.ConfigMapVolumeSource{
   611  				LocalObjectReference: apiv1.LocalObjectReference{
   612  					Name: "my_config",
   613  				},
   614  				Items: []apiv1.KeyToPath{
   615  					{
   616  						Key:  "file.html",
   617  						Path: "config-0",
   618  						Mode: &mode,
   619  					},
   620  				},
   621  			},
   622  		},
   623  	}
   624  
   625  	expectedMount := apiv1.VolumeMount{
   626  		Name:      "config-0",
   627  		ReadOnly:  true,
   628  		MountPath: "/usr/share/nginx/html/index.html",
   629  		SubPath:   "config-0",
   630  	}
   631  
   632  	assert.Len(t, podTemplate.Spec.Volumes, 1)
   633  	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
   634  	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
   635  	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
   636  }
   637  
   638  func TestToPodWithTargetlessFileBasedConfig(t *testing.T) {
   639  	podTemplate := podTemplate(t, Stack("demo",
   640  		WithService("nginx",
   641  			Image("nginx"),
   642  			WithConfig(
   643  				ConfigSource("myconfig"),
   644  			),
   645  		),
   646  		WithConfigObjConfig("myconfig", ConfigFile("./file.html")),
   647  	))
   648  
   649  	expectedVolume := apiv1.Volume{
   650  		Name: "config-0",
   651  		VolumeSource: apiv1.VolumeSource{
   652  			ConfigMap: &apiv1.ConfigMapVolumeSource{
   653  				LocalObjectReference: apiv1.LocalObjectReference{
   654  					Name: "myconfig",
   655  				},
   656  				Items: []apiv1.KeyToPath{
   657  					{
   658  						Key:  "file.html",
   659  						Path: "config-0",
   660  					},
   661  				},
   662  			},
   663  		},
   664  	}
   665  
   666  	expectedMount := apiv1.VolumeMount{
   667  		Name:      "config-0",
   668  		ReadOnly:  true,
   669  		MountPath: "/myconfig",
   670  		SubPath:   "config-0",
   671  	}
   672  
   673  	assert.Len(t, podTemplate.Spec.Volumes, 1)
   674  	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
   675  	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
   676  	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
   677  }
   678  
   679  func TestToPodWithExternalConfig(t *testing.T) {
   680  	podTemplate := podTemplate(t, Stack("demo",
   681  		WithService("nginx",
   682  			Image("nginx"),
   683  			WithConfig(
   684  				ConfigSource("my_config"),
   685  				ConfigTarget("/usr/share/nginx/html/index.html"),
   686  				ConfigUID("103"),
   687  				ConfigGID("103"),
   688  				ConfigMode(0440),
   689  			),
   690  		),
   691  		WithConfigObjConfig("my_config", ConfigExternal),
   692  	))
   693  
   694  	mode := int32(0440)
   695  
   696  	expectedVolume := apiv1.Volume{
   697  		Name: "config-0",
   698  		VolumeSource: apiv1.VolumeSource{
   699  			ConfigMap: &apiv1.ConfigMapVolumeSource{
   700  				LocalObjectReference: apiv1.LocalObjectReference{
   701  					Name: "my_config",
   702  				},
   703  				Items: []apiv1.KeyToPath{
   704  					{
   705  						Key:  "file", // TODO: This is the key we assume external config use
   706  						Path: "config-0",
   707  						Mode: &mode,
   708  					},
   709  				},
   710  			},
   711  		},
   712  	}
   713  
   714  	expectedMount := apiv1.VolumeMount{
   715  		Name:      "config-0",
   716  		ReadOnly:  true,
   717  		MountPath: "/usr/share/nginx/html/index.html",
   718  		SubPath:   "config-0",
   719  	}
   720  
   721  	assert.Len(t, podTemplate.Spec.Volumes, 1)
   722  	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
   723  	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
   724  	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
   725  }
   726  
   727  func TestToPodWithTwoConfigsSameMountPoint(t *testing.T) {
   728  	podTemplate := podTemplate(t, Stack("demo",
   729  		WithService("nginx",
   730  			Image("nginx"),
   731  			WithConfig(
   732  				ConfigSource("first"),
   733  				ConfigTarget("/data/first.json"),
   734  				ConfigMode(0440),
   735  			),
   736  			WithConfig(
   737  				ConfigSource("second"),
   738  				ConfigTarget("/data/second.json"),
   739  				ConfigMode(0550),
   740  			),
   741  		),
   742  		WithConfigObjConfig("first", ConfigFile("./file1")),
   743  		WithConfigObjConfig("second", ConfigFile("./file2")),
   744  	))
   745  
   746  	mode0440 := int32(0440)
   747  	mode0550 := int32(0550)
   748  
   749  	expectedVolumes := []apiv1.Volume{
   750  		{
   751  			Name: "config-0",
   752  			VolumeSource: apiv1.VolumeSource{
   753  				ConfigMap: &apiv1.ConfigMapVolumeSource{
   754  					LocalObjectReference: apiv1.LocalObjectReference{
   755  						Name: "first",
   756  					},
   757  					Items: []apiv1.KeyToPath{
   758  						{
   759  							Key:  "file1",
   760  							Path: "config-0",
   761  							Mode: &mode0440,
   762  						},
   763  					},
   764  				},
   765  			},
   766  		},
   767  		{
   768  			Name: "config-1",
   769  			VolumeSource: apiv1.VolumeSource{
   770  				ConfigMap: &apiv1.ConfigMapVolumeSource{
   771  					LocalObjectReference: apiv1.LocalObjectReference{
   772  						Name: "second",
   773  					},
   774  					Items: []apiv1.KeyToPath{
   775  						{
   776  							Key:  "file2",
   777  							Path: "config-1",
   778  							Mode: &mode0550,
   779  						},
   780  					},
   781  				},
   782  			},
   783  		},
   784  	}
   785  
   786  	expectedMounts := []apiv1.VolumeMount{
   787  		{
   788  			Name:      "config-0",
   789  			ReadOnly:  true,
   790  			MountPath: "/data/first.json",
   791  			SubPath:   "config-0",
   792  		},
   793  		{
   794  			Name:      "config-1",
   795  			ReadOnly:  true,
   796  			MountPath: "/data/second.json",
   797  			SubPath:   "config-1",
   798  		},
   799  	}
   800  
   801  	assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes)
   802  	assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts)
   803  }
   804  
   805  func TestToPodWithTwoExternalConfigsSameMountPoint(t *testing.T) {
   806  	podTemplate := podTemplate(t, Stack("demo",
   807  		WithService("nginx",
   808  			Image("nginx"),
   809  			WithConfig(
   810  				ConfigSource("first"),
   811  				ConfigTarget("/data/first.json"),
   812  			),
   813  			WithConfig(
   814  				ConfigSource("second"),
   815  				ConfigTarget("/data/second.json"),
   816  			),
   817  		),
   818  		WithConfigObjConfig("first", ConfigExternal),
   819  		WithConfigObjConfig("second", ConfigExternal),
   820  	))
   821  
   822  	expectedVolumes := []apiv1.Volume{
   823  		{
   824  			Name: "config-0",
   825  			VolumeSource: apiv1.VolumeSource{
   826  				ConfigMap: &apiv1.ConfigMapVolumeSource{
   827  					LocalObjectReference: apiv1.LocalObjectReference{
   828  						Name: "first",
   829  					},
   830  					Items: []apiv1.KeyToPath{
   831  						{
   832  							Key:  "file",
   833  							Path: "config-0",
   834  						},
   835  					},
   836  				},
   837  			},
   838  		},
   839  		{
   840  			Name: "config-1",
   841  			VolumeSource: apiv1.VolumeSource{
   842  				ConfigMap: &apiv1.ConfigMapVolumeSource{
   843  					LocalObjectReference: apiv1.LocalObjectReference{
   844  						Name: "second",
   845  					},
   846  					Items: []apiv1.KeyToPath{
   847  						{
   848  							Key:  "file",
   849  							Path: "config-1",
   850  						},
   851  					},
   852  				},
   853  			},
   854  		},
   855  	}
   856  
   857  	expectedMounts := []apiv1.VolumeMount{
   858  		{
   859  			Name:      "config-0",
   860  			ReadOnly:  true,
   861  			MountPath: "/data/first.json",
   862  			SubPath:   "config-0",
   863  		},
   864  		{
   865  			Name:      "config-1",
   866  			ReadOnly:  true,
   867  			MountPath: "/data/second.json",
   868  			SubPath:   "config-1",
   869  		},
   870  	}
   871  
   872  	assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes)
   873  	assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts)
   874  }
   875  
   876  func TestToPodWithPullSecret(t *testing.T) {
   877  	podTemplateWithSecret := podTemplate(t, Stack("demo",
   878  		WithService("nginx",
   879  			Image("nginx"),
   880  			PullSecret("test-pull-secret"),
   881  		)))
   882  	assert.Equal(t, 1, len(podTemplateWithSecret.Spec.ImagePullSecrets))
   883  	assert.Equal(t, "test-pull-secret", podTemplateWithSecret.Spec.ImagePullSecrets[0].Name)
   884  	podTemplateNoSecret := podTemplate(t, Stack("demo",
   885  		WithService("nginx",
   886  			Image("nginx"),
   887  		)))
   888  	assert.Nil(t, podTemplateNoSecret.Spec.ImagePullSecrets)
   889  }
   890  
   891  func TestToPodWithPullPolicy(t *testing.T) {
   892  	cases := []struct {
   893  		name           string
   894  		stack          *latest.Stack
   895  		expectedPolicy apiv1.PullPolicy
   896  		expectedError  string
   897  	}{
   898  		{
   899  			name: "specific tag",
   900  			stack: Stack("demo",
   901  				WithService("nginx",
   902  					Image("nginx:specific"),
   903  				)),
   904  			expectedPolicy: apiv1.PullIfNotPresent,
   905  		},
   906  		{
   907  			name: "latest tag",
   908  			stack: Stack("demo",
   909  				WithService("nginx",
   910  					Image("nginx:latest"),
   911  				)),
   912  			expectedPolicy: apiv1.PullAlways,
   913  		},
   914  		{
   915  			name: "explicit policy",
   916  			stack: Stack("demo",
   917  				WithService("nginx",
   918  					Image("nginx:latest"),
   919  					PullPolicy("Never"),
   920  				)),
   921  			expectedPolicy: apiv1.PullNever,
   922  		},
   923  		{
   924  			name: "invalid policy",
   925  			stack: Stack("demo",
   926  				WithService("nginx",
   927  					Image("nginx:latest"),
   928  					PullPolicy("Invalid"),
   929  				)),
   930  			expectedError: `invalid pull policy "Invalid", must be "Always", "IfNotPresent" or "Never"`,
   931  		},
   932  	}
   933  
   934  	for _, c := range cases {
   935  		t.Run(c.name, func(t *testing.T) {
   936  			pod, err := podTemplateWithError(c.stack)
   937  			if c.expectedError != "" {
   938  				assert.EqualError(t, err, c.expectedError)
   939  			} else {
   940  				assert.NoError(t, err)
   941  				assert.Equal(t, pod.Spec.Containers[0].ImagePullPolicy, c.expectedPolicy)
   942  			}
   943  		})
   944  	}
   945  }