Fixed secret file locations to match results from docker-compose when using file-based secrets

This commit is contained in:
Diogo de Campos 2022-01-19 12:44:41 +01:00
parent b796ec0222
commit acf24e94d5
3 changed files with 87 additions and 31 deletions

View File

@ -63,6 +63,12 @@ var (
ServiceGroupMode string ServiceGroupMode string
ServiceGroupName string ServiceGroupName string
// SecretsAsFiles forces secrets to result in files inside a container instead of symlinked directories containing
// files of the same name. This reproduces the behavior of file-based secrets in docker-compose and should probably
// be the default for kompose, but we must keep compatibility with the previous behavior.
// See https://github.com/kubernetes/kompose/issues/1280 for more details.
SecretsAsFiles bool
) )
var convertCmd = &cobra.Command{ var convertCmd = &cobra.Command{
@ -109,6 +115,7 @@ var convertCmd = &cobra.Command{
MultipleContainerMode: MultipleContainerMode, MultipleContainerMode: MultipleContainerMode,
ServiceGroupMode: ServiceGroupMode, ServiceGroupMode: ServiceGroupMode,
ServiceGroupName: ServiceGroupName, ServiceGroupName: ServiceGroupName,
SecretsAsFiles: SecretsAsFiles,
} }
if ServiceGroupMode == "" && MultipleContainerMode { if ServiceGroupMode == "" && MultipleContainerMode {
@ -145,6 +152,7 @@ func init() {
convertCmd.Flags().StringVar(&ServiceGroupMode, "service-group-mode", "", "Group multiple service to create single workload by `label`(`kompose.service.group`) or `volume`(shared volumes)") convertCmd.Flags().StringVar(&ServiceGroupMode, "service-group-mode", "", "Group multiple service to create single workload by `label`(`kompose.service.group`) or `volume`(shared volumes)")
convertCmd.Flags().StringVar(&ServiceGroupName, "service-group-name", "", "Using with --service-group-mode=volume to specific a final service name for the group") convertCmd.Flags().StringVar(&ServiceGroupName, "service-group-name", "", "Using with --service-group-mode=volume to specific a final service name for the group")
convertCmd.Flags().MarkDeprecated("multiple-container-mode", "use --service-group-mode=label") convertCmd.Flags().MarkDeprecated("multiple-container-mode", "use --service-group-mode=label")
convertCmd.Flags().BoolVar(&SecretsAsFiles, "secrets-as-files", false, "Always convert docker-compose secrets into files instead of symlinked directories.")
// OpenShift only // OpenShift only
convertCmd.Flags().BoolVar(&ConvertDeploymentConfig, "deployment-config", true, "Generate an OpenShift deploymentconfig object") convertCmd.Flags().BoolVar(&ConvertDeploymentConfig, "deployment-config", true, "Generate an OpenShift deploymentconfig object")

View File

@ -82,6 +82,7 @@ type ConvertOptions struct {
MultipleContainerMode bool MultipleContainerMode bool
ServiceGroupMode string ServiceGroupMode string
ServiceGroupName string ServiceGroupName string
SecretsAsFiles bool
} }
// IsPodController indicate if the user want to use a controller // IsPodController indicate if the user want to use a controller

View File

@ -29,6 +29,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/docker/cli/cli/compose/types"
"github.com/fatih/structs" "github.com/fatih/structs"
"github.com/kubernetes/kompose/pkg/kobject" "github.com/kubernetes/kompose/pkg/kobject"
"github.com/kubernetes/kompose/pkg/loader/compose" "github.com/kubernetes/kompose/pkg/loader/compose"
@ -751,7 +752,7 @@ func (k *Kubernetes) ConfigTmpfs(name string, service kobject.ServiceConfig) ([]
// In kubernetes' Secret resource, it has a data structure like a map[string]bytes, every key will act like the file name // In kubernetes' Secret resource, it has a data structure like a map[string]bytes, every key will act like the file name
// when mount to a container. This is the part that missing in compose. So we will create a single key secret from compose // when mount to a container. This is the part that missing in compose. So we will create a single key secret from compose
// config and the key's name will be the secret's name, it's value is the file content. // config and the key's name will be the secret's name, it's value is the file content.
// compose'secret can only be mounted at `/run/secrets`, so we will hardcoded this. // compose's secret can only be mounted at `/run/secrets`, so this will be hardcoded.
func (k *Kubernetes) ConfigSecretVolumes(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) { func (k *Kubernetes) ConfigSecretVolumes(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) {
var volumeMounts []api.VolumeMount var volumeMounts []api.VolumeMount
var volumes []api.Volume var volumes []api.Volume
@ -764,35 +765,11 @@ func (k *Kubernetes) ConfigSecretVolumes(name string, service kobject.ServiceCon
log.Warnf("Ignore gid in secrets for service: %s", name) log.Warnf("Ignore gid in secrets for service: %s", name)
} }
var itemPath string // should be the filename var secretItemPath, secretMountPath, secretSubPath string
var mountPath = "" // should be the directory if k.Opt.SecretsAsFiles {
// if is used the short-syntax secretItemPath, secretMountPath, secretSubPath = k.getSecretPaths(secretConfig)
if secretConfig.Target == "" {
// the secret path (mountPath) should be inside the default directory /run/secrets
mountPath = "/run/secrets/" + secretConfig.Source
// the itemPath should be the source itself
itemPath = secretConfig.Source
} else { } else {
// if is the long-syntax, i should get the last part of path and consider it the filename secretItemPath, secretMountPath, secretSubPath = k.getSecretPathsLegacy(secretConfig)
pathSplitted := strings.Split(secretConfig.Target, "/")
lastPart := pathSplitted[len(pathSplitted)-1]
// if the filename (lastPart) and the target is the same
if lastPart == secretConfig.Target {
// the secret path should be the source (it need to be inside a directory and only the filename was given)
mountPath = secretConfig.Source
} else {
// should then get the target without the filename (lastPart)
mountPath = mountPath + strings.TrimSuffix(secretConfig.Target, "/"+lastPart) // menos ultima parte
}
// if the target isn't absolute path
if strings.HasPrefix(secretConfig.Target, "/") == false {
// concat the default secret directory
mountPath = "/run/secrets/" + mountPath
}
itemPath = lastPart
} }
volSource := api.VolumeSource{ volSource := api.VolumeSource{
@ -800,7 +777,7 @@ func (k *Kubernetes) ConfigSecretVolumes(name string, service kobject.ServiceCon
SecretName: secretConfig.Source, SecretName: secretConfig.Source,
Items: []api.KeyToPath{{ Items: []api.KeyToPath{{
Key: secretConfig.Source, Key: secretConfig.Source,
Path: itemPath, Path: secretItemPath,
}}, }},
}, },
} }
@ -818,7 +795,8 @@ func (k *Kubernetes) ConfigSecretVolumes(name string, service kobject.ServiceCon
volMount := api.VolumeMount{ volMount := api.VolumeMount{
Name: vol.Name, Name: vol.Name,
MountPath: mountPath, MountPath: secretMountPath,
SubPath: secretSubPath,
} }
volumeMounts = append(volumeMounts, volMount) volumeMounts = append(volumeMounts, volMount)
} }
@ -826,6 +804,75 @@ func (k *Kubernetes) ConfigSecretVolumes(name string, service kobject.ServiceCon
return volumeMounts, volumes return volumeMounts, volumes
} }
func (k *Kubernetes) getSecretPaths(secretConfig types.ServiceSecretConfig) (secretItemPath, secretMountPath, secretSubPath string) {
// Default secretConfig.Target to secretConfig.Source, just in case user was using short secret syntax or
// otherwise did not define a specific target
target := secretConfig.Target
if target == "" {
target = secretConfig.Source
}
// If target is an absolute path, set that as the MountPath
if strings.HasPrefix(secretConfig.Target, "/") {
secretMountPath = target
} else {
// If target is a relative path, prefix with "/run/secrets/" to replicate what docker-compose would do
secretMountPath = "/run/secrets/" + target
}
// Set subPath to the target filename. this ensures that we end up with a file at our MountPath instead
// of a directory with symlinks (see https://stackoverflow.com/a/68332231)
splitPath := strings.Split(target, "/")
secretFilename := splitPath[len(splitPath)-1]
// `secretItemPath` and `secretSubPath` have to be the same as `secretFilename` to ensure we create a file with
// that name at `secretMountPath`, instead of a directory containing a symlink to the actual file.
secretItemPath = secretFilename
secretSubPath = secretFilename
return secretItemPath, secretMountPath, secretSubPath
}
func (k *Kubernetes) getSecretPathsLegacy(secretConfig types.ServiceSecretConfig) (secretItemPath, secretMountPath, secretSubPath string) {
// The old way of setting secret paths. It resulted in files being placed in incorrect locations when compared to
// docker-compose results, but some people might depend on this behavior so this is kept here for compatibility.
// See https://github.com/kubernetes/kompose/issues/1280 for more details.
var itemPath string // should be the filename
var mountPath = "" // should be the directory
// if is used the short-syntax
if secretConfig.Target == "" {
// the secret path (mountPath) should be inside the default directory /run/secrets
mountPath = "/run/secrets/" + secretConfig.Source
// the itemPath should be the source itself
itemPath = secretConfig.Source
} else {
// if is the long-syntax, i should get the last part of path and consider it the filename
pathSplitted := strings.Split(secretConfig.Target, "/")
lastPart := pathSplitted[len(pathSplitted)-1]
// if the filename (lastPart) and the target is the same
if lastPart == secretConfig.Target {
// the secret path should be the source (it need to be inside a directory and only the filename was given)
mountPath = secretConfig.Source
} else {
// should then get the target without the filename (lastPart)
mountPath = mountPath + strings.TrimSuffix(secretConfig.Target, "/"+lastPart) // menos ultima parte
}
// if the target isn't absolute path
if strings.HasPrefix(secretConfig.Target, "/") == false {
// concat the default secret directory
mountPath = "/run/secrets/" + mountPath
}
itemPath = lastPart
}
secretSubPath = "" // We didn't set a SubPath in legacy behavior
return itemPath, mountPath, ""
}
// ConfigVolumes configure the container volumes. // ConfigVolumes configure the container volumes.
func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume, []*api.PersistentVolumeClaim, []*api.ConfigMap, error) { func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume, []*api.PersistentVolumeClaim, []*api.ConfigMap, error) {
volumeMounts := []api.VolumeMount{} volumeMounts := []api.VolumeMount{}