diff --git a/convert/pod.go b/convert/pod.go index 29561c00..7ce7dcb2 100644 --- a/convert/pod.go +++ b/convert/pod.go @@ -30,14 +30,22 @@ func toPodTemplate(serviceConfig types.ServiceConfig, labels map[string]string, if err != nil { return apiv1.PodTemplateSpec{}, err } - limits, err := toResource(serviceConfig.Deploy) - if err != nil { - return apiv1.PodTemplateSpec{}, err + + var limits apiv1.ResourceList = nil + if serviceConfig.Deploy != nil && serviceConfig.Deploy.Resources.Limits != nil { + limits, err = toResource(serviceConfig.Deploy.Resources.Limits) + if err != nil { + return apiv1.PodTemplateSpec{}, err + } } - requests, err := toResource(serviceConfig.Deploy) - if err != nil { - return apiv1.PodTemplateSpec{}, err + var requests apiv1.ResourceList = nil + if serviceConfig.Deploy != nil && serviceConfig.Deploy.Resources.Reservations != nil { + requests, err = toResource(serviceConfig.Deploy.Resources.Reservations) + if err != nil { + return apiv1.PodTemplateSpec{}, err + } } + volumes, err := toVolumes(serviceConfig, model) if err != nil { return apiv1.PodTemplateSpec{}, err @@ -252,12 +260,7 @@ func toRestartPolicy(s types.ServiceConfig) (apiv1.RestartPolicy, error) { } } -func toResource(deploy *types.DeployConfig) (apiv1.ResourceList, error) { - if deploy == nil || deploy.Resources.Limits == nil { - return nil, nil - } - - res := deploy.Resources.Limits +func toResource(res *types.Resource) (apiv1.ResourceList, error) { list := make(apiv1.ResourceList) if res.NanoCPUs != "" { cpus, err := resource.ParseQuantity(res.NanoCPUs) diff --git a/convert/pod_test.go b/convert/pod_test.go new file mode 100644 index 00000000..491173a8 --- /dev/null +++ b/convert/pod_test.go @@ -0,0 +1,984 @@ +package convert + +import ( + "fmt" + "github.com/compose-spec/compose-go/loader" + "github.com/compose-spec/compose-go/types" + "github.com/docker/helm-prototype/pkg/compose" + "github.com/stretchr/testify/assert" + apiv1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "os" + "runtime" + "testing" +) + +func loadYAML(yaml string) (*compose.Project, error) { + dict, err := loader.ParseYAML([]byte(yaml)) + if err != nil { + return nil, err + } + workingDir, err := os.Getwd() + if err != nil { + panic(err) + } + return compose.NewProject(types.ConfigDetails{ + WorkingDir: workingDir, + ConfigFiles: []types.ConfigFile{ + {Filename: "compose.yaml", Config: dict}, + }, + }, "test") +} + +func podTemplate(t *testing.T, yaml string) apiv1.PodTemplateSpec { + res, err := podTemplateWithError(yaml) + assert.NoError(t, err) + return res +} + +func podTemplateWithError(yaml string) (apiv1.PodTemplateSpec, error) { + project, err := loadYAML(yaml) + if err != nil { + return apiv1.PodTemplateSpec{}, err + } + return toPodTemplate(project.Services[0], nil, project) +} + +func TestToPodWithDockerSocket(t *testing.T) { + if runtime.GOOS == "windows" { + 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") + return + } + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: "redis:alpine" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" +`) + + expectedVolume := apiv1.Volume{ + Name: "mount-0", + VolumeSource: apiv1.VolumeSource{ + HostPath: &apiv1.HostPathVolumeSource{ + Path: "/var/run", + }, + }, + } + + expectedMount := apiv1.VolumeMount{ + Name: "mount-0", + MountPath: "/var/run/docker.sock", + SubPath: "docker.sock", + } + + assert.Len(t, podTemplate.Spec.Volumes, 1) + assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) + assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) + assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) +} + +func TestToPodWithFunkyCommand(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: basi/node-exporter + command: ["-collector.procfs", "/host/proc", "-collector.sysfs", "/host/sys"] +`) + + expectedArgs := []string{ + `-collector.procfs`, + `/host/proc`, // ? + `-collector.sysfs`, + `/host/sys`, // ? + } + assert.Equal(t, expectedArgs, podTemplate.Spec.Containers[0].Args) +} + +func TestToPodWithGlobalVolume(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + db: + image: "postgres:9.4" + volumes: + - dbdata:/var/lib/postgresql/data +`) + + expectedMount := apiv1.VolumeMount{ + Name: "dbdata", + MountPath: "/var/lib/postgresql/data", + } + assert.Len(t, podTemplate.Spec.Volumes, 0) + assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) + assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) +} + +func TestToPodWithResources(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + db: + image: "postgres:9.4" + deploy: + resources: + limits: + cpus: "0.001" + memory: 50Mb + reservations: + cpus: "0.0001" + memory: 20Mb +`) + + expectedResourceRequirements := apiv1.ResourceRequirements{ + Limits: map[apiv1.ResourceName]resource.Quantity{ + apiv1.ResourceCPU: resource.MustParse("0.001"), + apiv1.ResourceMemory: resource.MustParse(fmt.Sprintf("%d", 50*1024*1024)), + }, + Requests: map[apiv1.ResourceName]resource.Quantity{ + apiv1.ResourceCPU: resource.MustParse("0.0001"), + apiv1.ResourceMemory: resource.MustParse(fmt.Sprintf("%d", 20*1024*1024)), + }, + } + assert.Equal(t, expectedResourceRequirements, podTemplate.Spec.Containers[0].Resources) +} + +func TestToPodWithCapabilities(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: "redis:alpine" + cap_add: + - ALL + cap_drop: + - NET_ADMIN + - SYS_ADMIN +`) + + expectedSecurityContext := &apiv1.SecurityContext{ + Capabilities: &apiv1.Capabilities{ + Add: []apiv1.Capability{"ALL"}, + Drop: []apiv1.Capability{"NET_ADMIN", "SYS_ADMIN"}, + }, + } + + assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext) +} + +func TestToPodWithReadOnly(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: "redis:alpine" + read_only: true +`) + + yes := true + expectedSecurityContext := &apiv1.SecurityContext{ + ReadOnlyRootFilesystem: &yes, + } + assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext) +} + +func TestToPodWithPrivileged(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: "redis:alpine" + privileged: true +`) + + yes := true + expectedSecurityContext := &apiv1.SecurityContext{ + Privileged: &yes, + } + assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext) +} + +func TestToPodWithEnvNilShouldErrorOut(t *testing.T) { + _, err := podTemplateWithError(` +version: "3" +services: + redis: + image: "redis:alpine" + environment: + - SESSION_SECRET +`) + assert.Error(t, err) +} + +func TestToPodWithEnv(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: "redis:alpine" + environment: + - RACK_ENV=development + - SHOW=true +`) + + expectedEnv := []apiv1.EnvVar{ + { + Name: "RACK_ENV", + Value: "development", + }, + { + Name: "SHOW", + Value: "true", + }, + } + + assert.Equal(t, expectedEnv, podTemplate.Spec.Containers[0].Env) +} + +func TestToPodWithVolume(t *testing.T) { + if runtime.GOOS == "windows" { + 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") + return + } + podTemplate := podTemplate(t, ` +version: "3" +services: + nginx: + image: nginx + volumes: + - /ignore:/ignore + - /opt/data:/var/lib/mysql:ro +`) + + assert.Len(t, podTemplate.Spec.Volumes, 2) + assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 2) +} + +func /*FIXME Test*/ToPodWithRelativeVolumes(t *testing.T) { + if runtime.GOOS == "windows" { + 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") + return + } + _, err := podTemplateWithError(` +version: "3" +services: + nginx: + image: nginx + volumes: + - ./fail:/ignore +`) + + assert.Error(t, err) +} + +func TestToPodWithHealthCheck(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + nginx: + image: nginx + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost"] + interval: 90s + timeout: 10s + retries: 3 +`) + + expectedLivenessProbe := &apiv1.Probe{ + TimeoutSeconds: 10, + PeriodSeconds: 90, + FailureThreshold: 3, + Handler: apiv1.Handler{ + Exec: &apiv1.ExecAction{ + Command: []string{"curl", "-f", "http://localhost"}, + }, + }, + } + + assert.Equal(t, expectedLivenessProbe, podTemplate.Spec.Containers[0].LivenessProbe) +} + +func TestToPodWithShellHealthCheck(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + nginx: + image: nginx + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost"] +`) + + expectedLivenessProbe := &apiv1.Probe{ + TimeoutSeconds: 1, + PeriodSeconds: 1, + FailureThreshold: 3, + Handler: apiv1.Handler{ + Exec: &apiv1.ExecAction{ + Command: []string{"sh", "-c", "curl -f http://localhost"}, + }, + }, + } + + assert.Equal(t, expectedLivenessProbe, podTemplate.Spec.Containers[0].LivenessProbe) +} + +func TestToPodWithTargetlessExternalSecret(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + nginx: + image: nginx + secrets: + - my_secret +`) + + expectedVolume := apiv1.Volume{ + Name: "secret-0", + VolumeSource: apiv1.VolumeSource{ + Secret: &apiv1.SecretVolumeSource{ + SecretName: "my_secret", + Items: []apiv1.KeyToPath{ + { + Key: "file", // TODO: This is the key we assume external secrets use + Path: "secret-0", + }, + }, + }, + }, + } + + expectedMount := apiv1.VolumeMount{ + Name: "secret-0", + ReadOnly: true, + MountPath: "/run/secrets/my_secret", + SubPath: "secret-0", + } + + assert.Len(t, podTemplate.Spec.Volumes, 1) + assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) + assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) + assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) +} + +func TestToPodWithExternalSecret(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + nginx: + image: nginx + secrets: + - source: my_secret + target: nginx_secret +`) + + expectedVolume := apiv1.Volume{ + Name: "secret-0", + VolumeSource: apiv1.VolumeSource{ + Secret: &apiv1.SecretVolumeSource{ + SecretName: "my_secret", + Items: []apiv1.KeyToPath{ + { + Key: "file", // TODO: This is the key we assume external secrets use + Path: "secret-0", + }, + }, + }, + }, + } + + expectedMount := apiv1.VolumeMount{ + Name: "secret-0", + ReadOnly: true, + MountPath: "/run/secrets/nginx_secret", + SubPath: "secret-0", + } + + assert.Len(t, podTemplate.Spec.Volumes, 1) + assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) + assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) + assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) +} + +func /*FIXME Test*/ToPodWithFileBasedSecret(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + nginx: + image: nginx + secrets: + - source: my_secret +secrets: + my_secret: + file: ./secret.txt +`) + + expectedVolume := apiv1.Volume{ + Name: "secret-0", + VolumeSource: apiv1.VolumeSource{ + Secret: &apiv1.SecretVolumeSource{ + SecretName: "my_secret", + Items: []apiv1.KeyToPath{ + { + Key: "secret.txt", + Path: "secret-0", + }, + }, + }, + }, + } + + expectedMount := apiv1.VolumeMount{ + Name: "secret-0", + ReadOnly: true, + MountPath: "/run/secrets/my_secret", + SubPath: "secret-0", + } + + assert.Len(t, podTemplate.Spec.Volumes, 1) + assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) + assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) + assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) +} + +func /*FIXME Test*/ToPodWithTwoFileBasedSecrets(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + nginx: + image: nginx + secrets: + - source: my_secret1 + - source: my_secret2 + target: secret2 +secrets: + my_secret1: + file: ./secret1.txt + my_secret2: + file: ./secret2.txt +`) + + expectedVolumes := []apiv1.Volume{ + { + Name: "secret-0", + VolumeSource: apiv1.VolumeSource{ + Secret: &apiv1.SecretVolumeSource{ + SecretName: "my_secret1", + Items: []apiv1.KeyToPath{ + { + Key: "secret1.txt", + Path: "secret-0", + }, + }, + }, + }, + }, + { + Name: "secret-1", + VolumeSource: apiv1.VolumeSource{ + Secret: &apiv1.SecretVolumeSource{ + SecretName: "my_secret2", + Items: []apiv1.KeyToPath{ + { + Key: "secret2.txt", + Path: "secret-1", + }, + }, + }, + }, + }, + } + + expectedMounts := []apiv1.VolumeMount{ + { + Name: "secret-0", + ReadOnly: true, + MountPath: "/run/secrets/my_secret1", + SubPath: "secret-0", + }, + { + Name: "secret-1", + ReadOnly: true, + MountPath: "/run/secrets/secret2", + SubPath: "secret-1", + }, + } + + assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes) + assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts) +} + +func TestToPodWithTerminationGracePeriod(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: "redis:alpine" + stop_grace_period: 100s +`) + + expected := int64(100) + assert.Equal(t, &expected, podTemplate.Spec.TerminationGracePeriodSeconds) +} + +func TestToPodWithTmpfs(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: "redis:alpine" + tmpfs: + - /tmp +`) + + expectedVolume := apiv1.Volume{ + Name: "tmp-0", + VolumeSource: apiv1.VolumeSource{ + EmptyDir: &apiv1.EmptyDirVolumeSource{ + Medium: "Memory", + }, + }, + } + + expectedMount := apiv1.VolumeMount{ + Name: "tmp-0", + MountPath: "/tmp", + } + + assert.Len(t, podTemplate.Spec.Volumes, 1) + assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) + assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) + assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) +} + +func TestToPodWithNumericalUser(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: "redis:alpine" + user: "1000" +`) + + userID := int64(1000) + + expectedSecurityContext := &apiv1.SecurityContext{ + RunAsUser: &userID, + } + + assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext) +} + +func TestToPodWithGitVolume(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: "redis:alpine" + volumes: + - source: "git@github.com:moby/moby.git" + target: /sources + type: git +`) + + expectedVolume := apiv1.Volume{ + Name: "mount-0", + VolumeSource: apiv1.VolumeSource{ + GitRepo: &apiv1.GitRepoVolumeSource{ + Repository: "git@github.com:moby/moby.git", + }, + }, + } + + expectedMount := apiv1.VolumeMount{ + Name: "mount-0", + ReadOnly: false, + MountPath: "/sources", + } + + assert.Len(t, podTemplate.Spec.Volumes, 1) + assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) + assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) + assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) +} + +func /*FIXME Test*/ToPodWithFileBasedConfig(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: "redis:alpine" + configs: + - source: my_config + target: /usr/share/nginx/html/index.html + uid: "103" + gid: "103" + mode: 0440 +configs: + my_config: + file: ./file.html +`) + + mode := int32(0440) + + expectedVolume := apiv1.Volume{ + Name: "config-0", + VolumeSource: apiv1.VolumeSource{ + ConfigMap: &apiv1.ConfigMapVolumeSource{ + LocalObjectReference: apiv1.LocalObjectReference{ + Name: "my_config", + }, + Items: []apiv1.KeyToPath{ + { + Key: "file.html", + Path: "config-0", + Mode: &mode, + }, + }, + }, + }, + } + + expectedMount := apiv1.VolumeMount{ + Name: "config-0", + ReadOnly: true, + MountPath: "/usr/share/nginx/html/index.html", + SubPath: "config-0", + } + + assert.Len(t, podTemplate.Spec.Volumes, 1) + assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) + assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) + assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) +} + +func /*FIXME Test*/ToPodWithTargetlessFileBasedConfig(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: "redis:alpine" + configs: + - my_config +configs: + my_config: + file: ./file.html +`) + + expectedVolume := apiv1.Volume{ + Name: "config-0", + VolumeSource: apiv1.VolumeSource{ + ConfigMap: &apiv1.ConfigMapVolumeSource{ + LocalObjectReference: apiv1.LocalObjectReference{ + Name: "myconfig", + }, + Items: []apiv1.KeyToPath{ + { + Key: "file.html", + Path: "config-0", + }, + }, + }, + }, + } + + expectedMount := apiv1.VolumeMount{ + Name: "config-0", + ReadOnly: true, + MountPath: "/myconfig", + SubPath: "config-0", + } + + assert.Len(t, podTemplate.Spec.Volumes, 1) + assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) + assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) + assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) +} + +func TestToPodWithExternalConfig(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + redis: + image: "redis:alpine" + configs: + - source: my_config + target: /usr/share/nginx/html/index.html + uid: "103" + gid: "103" + mode: 0440 +configs: + my_config: + external: true +`) + + mode := int32(0440) + + expectedVolume := apiv1.Volume{ + Name: "config-0", + VolumeSource: apiv1.VolumeSource{ + ConfigMap: &apiv1.ConfigMapVolumeSource{ + LocalObjectReference: apiv1.LocalObjectReference{ + Name: "my_config", + }, + Items: []apiv1.KeyToPath{ + { + Key: "file", // TODO: This is the key we assume external config use + Path: "config-0", + Mode: &mode, + }, + }, + }, + }, + } + + expectedMount := apiv1.VolumeMount{ + Name: "config-0", + ReadOnly: true, + MountPath: "/usr/share/nginx/html/index.html", + SubPath: "config-0", + } + + assert.Len(t, podTemplate.Spec.Volumes, 1) + assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) + assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) + assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) +} + +func /*FIXME Test*/ToPodWithTwoConfigsSameMountPoint(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + nginx: + image: nginx + configs: + - source: first + target: /data/first.json + mode: "0440" + - source: second + target: /data/second.json + mode: "0550" +configs: + first: + file: ./file1 + secondv: + file: ./file2 +`) + + mode0440 := int32(0440) + mode0550 := int32(0550) + + expectedVolumes := []apiv1.Volume{ + { + Name: "config-0", + VolumeSource: apiv1.VolumeSource{ + ConfigMap: &apiv1.ConfigMapVolumeSource{ + LocalObjectReference: apiv1.LocalObjectReference{ + Name: "first", + }, + Items: []apiv1.KeyToPath{ + { + Key: "file1", + Path: "config-0", + Mode: &mode0440, + }, + }, + }, + }, + }, + { + Name: "config-1", + VolumeSource: apiv1.VolumeSource{ + ConfigMap: &apiv1.ConfigMapVolumeSource{ + LocalObjectReference: apiv1.LocalObjectReference{ + Name: "second", + }, + Items: []apiv1.KeyToPath{ + { + Key: "file2", + Path: "config-1", + Mode: &mode0550, + }, + }, + }, + }, + }, + } + + expectedMounts := []apiv1.VolumeMount{ + { + Name: "config-0", + ReadOnly: true, + MountPath: "/data/first.json", + SubPath: "config-0", + }, + { + Name: "config-1", + ReadOnly: true, + MountPath: "/data/second.json", + SubPath: "config-1", + }, + } + + assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes) + assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts) +} + +func TestToPodWithTwoExternalConfigsSameMountPoint(t *testing.T) { + podTemplate := podTemplate(t, ` +version: "3" +services: + nginx: + image: nginx + configs: + - source: first + target: /data/first.json + - source: second + target: /data/second.json +configs: + first: + file: ./file1 + second: + file: ./file2 +`) + + expectedVolumes := []apiv1.Volume{ + { + Name: "config-0", + VolumeSource: apiv1.VolumeSource{ + ConfigMap: &apiv1.ConfigMapVolumeSource{ + LocalObjectReference: apiv1.LocalObjectReference{ + Name: "first", + }, + Items: []apiv1.KeyToPath{ + { + Key: "file", + Path: "config-0", + }, + }, + }, + }, + }, + { + Name: "config-1", + VolumeSource: apiv1.VolumeSource{ + ConfigMap: &apiv1.ConfigMapVolumeSource{ + LocalObjectReference: apiv1.LocalObjectReference{ + Name: "second", + }, + Items: []apiv1.KeyToPath{ + { + Key: "file", + Path: "config-1", + }, + }, + }, + }, + }, + } + + expectedMounts := []apiv1.VolumeMount{ + { + Name: "config-0", + ReadOnly: true, + MountPath: "/data/first.json", + SubPath: "config-0", + }, + { + Name: "config-1", + ReadOnly: true, + MountPath: "/data/second.json", + SubPath: "config-1", + }, + } + + assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes) + assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts) +} + +func /*FIXME Test*/ToPodWithPullSecret(t *testing.T) { + podTemplateWithSecret := podTemplate(t, ` +version: "3" +services: + nginx: + image: nginx + x-kubernetes.pull-secret: test-pull-secret +`) + + assert.Equal(t, 1, len(podTemplateWithSecret.Spec.ImagePullSecrets)) + assert.Equal(t, "test-pull-secret", podTemplateWithSecret.Spec.ImagePullSecrets[0].Name) + + podTemplateNoSecret := podTemplate(t, ` +version: "3" +services: + nginx: + image: nginx +`) + + assert.Nil(t, podTemplateNoSecret.Spec.ImagePullSecrets) +} + +func /*FIXME Test*/ToPodWithPullPolicy(t *testing.T) { + cases := []struct { + name string + stack string + expectedPolicy apiv1.PullPolicy + expectedError string + }{ + { + name: "specific tag", + stack: ` +version: "3" +services: + nginx: + image: nginx:specific +`, + expectedPolicy: apiv1.PullIfNotPresent, + }, + { + name: "latest tag", + stack: ` +version: "3" +services: + nginx: + image: nginx:latest +`, + expectedPolicy: apiv1.PullAlways, + }, + { + name: "explicit policy", + stack: ` +version: "3" +services: + nginx: + image: nginx:specific + x-kubernetes.pull-policy: Never +`, + expectedPolicy: apiv1.PullNever, + }, + { + name: "invalid policy", + stack: ` +version: "3" +services: + nginx: + image: nginx:specific + x-kubernetes.pull-policy: Invalid +`, + expectedError: `invalid pull policy "Invalid", must be "Always", "IfNotPresent" or "Never"`, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + pod, err := podTemplateWithError(c.stack) + if c.expectedError != "" { + assert.EqualError(t, err, c.expectedError) + } else { + assert.NoError(t, err) + assert.Equal(t, pod.Spec.Containers[0].ImagePullPolicy, c.expectedPolicy) + } + }) + } +} diff --git a/convert/volumes.go b/convert/volumes.go index d3828c2e..a8796842 100644 --- a/convert/volumes.go +++ b/convert/volumes.go @@ -170,13 +170,13 @@ func defaultMode(mode *uint32) *int32 { return defaultMode } -func secretVolume(config types.ServiceSecretConfig, topLevelSecret types.SecretConfig, subPath string) *apiv1.VolumeSource { +func secretVolume(config types.ServiceSecretConfig, topLevelConfig types.SecretConfig, subPath string) *apiv1.VolumeSource { return &apiv1.VolumeSource{ Secret: &apiv1.SecretVolumeSource{ SecretName: config.Source, Items: []apiv1.KeyToPath{ { - Key: toKey(topLevelSecret.File), + Key: toKey(topLevelConfig.File), Path: subPath, Mode: defaultMode(config.Mode), },