Fix environment variables interpolation (#1524)

This commit is contained in:
AhmedGrati 2022-11-19 03:35:33 +01:00 committed by GitHub
parent 089e946489
commit acb8046f84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 288 additions and 18 deletions

View File

@ -91,6 +91,7 @@ func (opt *ConvertOptions) IsPodController() bool {
return opt.IsDeploymentFlag || opt.IsDaemonSetFlag || opt.IsReplicationControllerFlag || opt.Controller != ""
}
// ServiceConfigGroup holds an array of a ServiceConfig objects.
type ServiceConfigGroup []ServiceConfig
// ServiceConfig holds the basic struct of a container

View File

@ -105,7 +105,9 @@ func parseV3(files []string) (kobject.KomposeObject, error) {
// We load it in order to retrieve the parsed output configuration!
// This will output a github.com/docker/cli ServiceConfig
// Which is similar to our version of ServiceConfig
currentConfig, err := loader.Load(configDetails)
currentConfig, err := loader.Load(configDetails, func(options *loader.Options) {
options.SkipInterpolation = true
})
if err != nil {
return kobject.KomposeObject{}, err
}
@ -129,7 +131,6 @@ func parseV3(files []string) (kobject.KomposeObject, error) {
if err != nil {
return kobject.KomposeObject{}, err
}
return komposeObject, nil
}

View File

@ -25,6 +25,7 @@ import (
"path"
"path/filepath"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
@ -382,6 +383,7 @@ func (k *Kubernetes) initSvcObject(name string, service kobject.ServiceConfig, p
return svc
}
// CreateLBService creates a k8s Load Balancer Service
func (k *Kubernetes) CreateLBService(name string, service kobject.ServiceConfig) []*api.Service {
var svcs []*api.Service
tcpPorts, udpPorts := k.ConfigLBServicePorts(service)
@ -442,6 +444,8 @@ func (k *Kubernetes) CreateHeadlessService(name string, service kobject.ServiceC
return svc
}
// UpdateKubernetesObjectsMultipleContainers method updates the kubernetes objects with the necessary data
func (k *Kubernetes) UpdateKubernetesObjectsMultipleContainers(name string, service kobject.ServiceConfig, objects *[]runtime.Object, podSpec PodSpec) error {
// Configure annotations
annotations := transformer.ConfigAnnotations(service)
@ -524,7 +528,7 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
template.Spec.Containers[0].Name = GetContainerName(service)
template.Spec.Containers[0].Env = envs
template.Spec.Containers[0].Command = service.Command
template.Spec.Containers[0].Args = service.Args
template.Spec.Containers[0].Args = GetContainerArgs(service)
template.Spec.Containers[0].WorkingDir = service.WorkingDir
template.Spec.Containers[0].VolumeMounts = append(template.Spec.Containers[0].VolumeMounts, volumesMount...)
template.Spec.Containers[0].Stdin = service.Stdin
@ -594,18 +598,18 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
template.ObjectMeta.Labels = transformer.ConfigLabelsWithNetwork(name, service.Network)
// Configure the image pull policy
if policy, err := GetImagePullPolicy(name, service.ImagePullPolicy); err != nil {
policy, err := GetImagePullPolicy(name, service.ImagePullPolicy)
if err != nil {
return err
} else {
template.Spec.Containers[0].ImagePullPolicy = policy
}
template.Spec.Containers[0].ImagePullPolicy = policy
// Configure the container restart policy.
if restart, err := GetRestartPolicy(name, service.Restart); err != nil {
restart, err := GetRestartPolicy(name, service.Restart)
if err != nil {
return err
} else {
template.Spec.RestartPolicy = restart
}
template.Spec.RestartPolicy = restart
// Configure hostname/domain_name settings
if service.HostName != "" {
@ -887,6 +891,7 @@ func FormatContainerName(name string) string {
return name
}
// GetContainerName returns the name of the container, from the service config object
func GetContainerName(service kobject.ServiceConfig) string {
name := service.Name
if len(service.ContainerName) > 0 {
@ -899,3 +904,15 @@ func GetContainerName(service kobject.ServiceConfig) string {
func FormatResourceName(name string) string {
return strings.ToLower(strings.Replace(name, "_", "-", -1))
}
// GetContainerArgs update the interpolation of env variables if exists.
// example: [curl, $PROTOCOL://$DOMAIN] => [curl, $(PROTOCOL)://$(DOMAIN)]
func GetContainerArgs(service kobject.ServiceConfig) []string {
var args []string
re := regexp.MustCompile(`\$([a-zA-Z0-9]*)`)
for _, arg := range service.Args {
arg = re.ReplaceAllString(arg, `$($1)`)
args = append(args, arg)
}
return args
}

View File

@ -597,3 +597,35 @@ func TestCreateServiceWithSpecialName(t *testing.T) {
}
}
}
func TestArgsInterpolation(t *testing.T) {
// An example service
service := kobject.ServiceConfig{
ContainerName: "name",
Image: "image",
Environment: []kobject.EnvVar{{Name: "PROTOCOL", Value: "https"}, {Name: "DOMAIN", Value: "google.com"}},
Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}},
Command: []string{"curl"},
Args: []string{"$PROTOCOL://$DOMAIN/"},
}
// An example object generated via k8s runtime.Objects()
komposeObject := kobject.KomposeObject{
ServiceConfigs: map[string]kobject.ServiceConfig{"app": service},
}
k := Kubernetes{}
objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 3})
if err != nil {
t.Error(errors.Wrap(err, "k.Transform failed"))
}
expectedArgs := []string{"$(PROTOCOL)://$(DOMAIN)/"}
for _, obj := range objects {
if deployment, ok := obj.(*appsv1.Deployment); ok {
args := deployment.Spec.Template.Spec.Containers[0].Args[0]
if args != expectedArgs[0] {
t.Errorf("Expected args %v upon conversion, actual %v", expectedArgs, args)
}
}
}
}

View File

@ -58,6 +58,7 @@ type Kubernetes struct {
// PVCRequestSize (Persistent Volume Claim) has default size
const PVCRequestSize = "100Mi"
// ValidVolumeSet has the different types of valid volumes
var ValidVolumeSet = map[string]struct{}{"emptyDir": {}, "hostPath": {}, "configMap": {}, "persistentVolumeClaim": {}}
const (
@ -432,6 +433,7 @@ func (k *Kubernetes) InitDS(name string, service kobject.ServiceConfig) *appsv1.
return ds
}
// InitSS method initialize a stateful set
func (k *Kubernetes) InitSS(name string, service kobject.ServiceConfig, replicas int) *appsv1.StatefulSet {
var podSpec api.PodSpec
if len(service.Configs) > 0 {
@ -634,6 +636,7 @@ func ConfigPorts(service kobject.ServiceConfig) []api.ContainerPort {
return ports
}
// ConfigLBServicePorts method configure the ports of the k8s Load Balancer Service
func (k *Kubernetes) ConfigLBServicePorts(service kobject.ServiceConfig) ([]api.ServicePort, []api.ServicePort) {
var tcpPorts []api.ServicePort
var udpPorts []api.ServicePort
@ -954,17 +957,16 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) (
volsource = source
} else if useConfigMap {
log.Debugf("Use configmap volume")
if cm, err := k.IntiConfigMapFromFileOrDir(name, volumeName, volume.Host, service); err != nil {
cm, err := k.IntiConfigMapFromFileOrDir(name, volumeName, volume.Host, service)
if err != nil {
return nil, nil, nil, nil, err
} else {
}
cms = append(cms, cm)
volsource = k.ConfigConfigMapVolumeSource(volumeName, volume.Container, cm)
if useSubPathMount(cm) {
volMount.SubPath = volsource.ConfigMap.Items[0].Path
}
}
} else {
volsource = k.ConfigPVCVolumeSource(volumeName, readonly)
if volume.VFrom == "" {

View File

@ -13,12 +13,15 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
)
// PodSpec holds the spec of k8s pod.
type PodSpec struct {
api.PodSpec
}
// PodSpecOption holds the function to apply on a PodSpec
type PodSpecOption func(*PodSpec)
// AddContainer method is responsible for adding a new container to a k8s Pod.
func AddContainer(service kobject.ServiceConfig, opt kobject.ConvertOptions) PodSpecOption {
return func(podSpec *PodSpec) {
name := GetContainerName(service)
@ -50,6 +53,7 @@ func AddContainer(service kobject.ServiceConfig, opt kobject.ConvertOptions) Pod
}
}
// TerminationGracePeriodSeconds method is responsible for attributing the grace period seconds option to a pod
func TerminationGracePeriodSeconds(name string, service kobject.ServiceConfig) PodSpecOption {
return func(podSpec *PodSpec) {
var err error
@ -156,6 +160,7 @@ func SecurityContext(name string, service kobject.ServiceConfig) PodSpecOption {
}
}
// SetVolumeNames method return a set of volume names
func SetVolumeNames(volumes []api.Volume) mapset.Set {
set := mapset.NewSet()
for _, volume := range volumes {
@ -164,6 +169,7 @@ func SetVolumeNames(volumes []api.Volume) mapset.Set {
return set
}
// SetVolumes method returns a method that adds the volumes to the pod spec
func SetVolumes(volumes []api.Volume) PodSpecOption {
return func(podSpec *PodSpec) {
volumesSet := SetVolumeNames(volumes)
@ -179,6 +185,7 @@ func SetVolumes(volumes []api.Volume) PodSpecOption {
}
}
// SetVolumeMountPaths method returns a set of volumes mount path
func SetVolumeMountPaths(volumesMount []api.VolumeMount) mapset.Set {
set := mapset.NewSet()
for _, volumeMount := range volumesMount {
@ -188,6 +195,7 @@ func SetVolumeMountPaths(volumesMount []api.VolumeMount) mapset.Set {
return set
}
// SetVolumeMounts returns a function which adds the volume mounts option to the pod spec
func SetVolumeMounts(volumesMount []api.VolumeMount) PodSpecOption {
return func(podSpec *PodSpec) {
volumesMountSet := SetVolumeMountPaths(volumesMount)
@ -242,6 +250,7 @@ func RestartPolicy(name string, service kobject.ServiceConfig) PodSpecOption {
}
}
// HostName configure the host name of a pod
func HostName(service kobject.ServiceConfig) PodSpecOption {
return func(podSpec *PodSpec) {
// Configure hostname/domain_name settings
@ -251,6 +260,7 @@ func HostName(service kobject.ServiceConfig) PodSpecOption {
}
}
// DomainName configure the domain name of a pod
func DomainName(service kobject.ServiceConfig) PodSpecOption {
return func(podSpec *PodSpec) {
if service.DomainName != "" {
@ -299,18 +309,21 @@ func configProbe(healthCheck kobject.HealthCheck) *api.Probe {
return &probe
}
// ServiceAccountName is responsible for setting the service account name to the pod spec
func ServiceAccountName(serviceAccountName string) PodSpecOption {
return func(podSpec *PodSpec) {
podSpec.ServiceAccountName = serviceAccountName
}
}
// TopologySpreadConstraints is responsible for setting the topology spread constraints to the pod spec
func TopologySpreadConstraints(service kobject.ServiceConfig) PodSpecOption {
return func(podSpec *PodSpec) {
podSpec.TopologySpreadConstraints = ConfigTopologySpreadConstraints(service)
}
}
// Append is responsible for adding the pod spec options to the particular pod
func (podSpec *PodSpec) Append(ops ...PodSpecOption) *PodSpec {
for _, option := range ops {
option(podSpec)
@ -318,6 +331,7 @@ func (podSpec *PodSpec) Append(ops ...PodSpecOption) *PodSpec {
return podSpec
}
// Get is responsible for returning the pod spec of a particular pod
func (podSpec *PodSpec) Get() api.PodSpec {
return podSpec.PodSpec
}

View File

@ -33,6 +33,7 @@ type Image struct {
Remote string // the image's remote identifier. (ie: registry/name[:tag])
}
// NewImageFromParsed method returns the docker image from the docker parser reference
func NewImageFromParsed(parsed *dockerparser.Reference) Image {
return Image{
Name: parsed.Name(),

View File

@ -27,6 +27,7 @@ type Tag struct {
Client dockerlib.Client
}
// TagImage function is responsible for tagging the docker image
func (c *Tag) TagImage(image Image) error {
options := dockerlib.TagImageOptions{
Tag: image.Tag,

View File

@ -203,3 +203,11 @@ k8s_output="$KOMPOSE_ROOT/script/test/fixtures/multiple-type-volumes/output-k8s.
os_output="$KOMPOSE_ROOT/script/test/fixtures/multiple-type-volumes/output-os.json"
convert::expect_success_and_warning "$k8s_cmd" "$k8s_output"
convert::expect_success "$os_cmd" "$os_output"
# Test environment variables interpolation
k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/envvars-interpolation/docker-compose.yaml convert --stdout -j --with-kompose-annotation=false"
os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/envvars-interpolation/docker-compose.yaml convert --stdout -j --with-kompose-annotation=false"
k8s_output="$KOMPOSE_ROOT/script/test/fixtures/envvars-interpolation/output-k8s.json"
os_output="$KOMPOSE_ROOT/script/test/fixtures/envvars-interpolation/output-os.json"
convert::expect_success_and_warning "$k8s_cmd" "$k8s_output"
convert::expect_success "$os_cmd" "$os_output"

View File

@ -0,0 +1,13 @@
version: '3.5'
services:
myservice:
image: alpine
environment:
PROTOCOL: 'https'
DOMAIN: 'google.com'
command:
[
'curl',
'$PROTOCOL://$DOMAIN/',
]

View File

@ -0,0 +1,60 @@
{
"kind": "List",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"kind": "Deployment",
"apiVersion": "apps/v1",
"metadata": {
"name": "myservice",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "myservice"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"io.kompose.service": "myservice"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"io.kompose.service": "myservice"
}
},
"spec": {
"containers": [
{
"name": "myservice",
"image": "alpine",
"args": [
"curl",
"$(PROTOCOL)://$(DOMAIN)/"
],
"env": [
{
"name": "DOMAIN",
"value": "google.com"
},
{
"name": "PROTOCOL",
"value": "https"
}
],
"resources": {}
}
],
"restartPolicy": "Always"
}
},
"strategy": {}
},
"status": {}
}
]
}

View File

@ -0,0 +1,120 @@
{
"kind": "List",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"kind": "DeploymentConfig",
"apiVersion": "v1",
"metadata": {
"name": "myservice",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "myservice"
}
},
"spec": {
"strategy": {
"resources": {}
},
"triggers": [
{
"type": "ConfigChange"
},
{
"type": "ImageChange",
"imageChangeParams": {
"automatic": true,
"containerNames": [
"myservice"
],
"from": {
"kind": "ImageStreamTag",
"name": "myservice:latest"
}
}
}
],
"replicas": 1,
"test": false,
"selector": {
"io.kompose.service": "myservice"
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"io.kompose.service": "myservice"
}
},
"spec": {
"containers": [
{
"name": "myservice",
"image": " ",
"args": [
"curl",
"$(PROTOCOL)://$(DOMAIN)/"
],
"env": [
{
"name": "DOMAIN",
"value": "google.com"
},
{
"name": "PROTOCOL",
"value": "https"
}
],
"resources": {}
}
],
"restartPolicy": "Always"
}
}
},
"status": {
"latestVersion": 0,
"observedGeneration": 0,
"replicas": 0,
"updatedReplicas": 0,
"availableReplicas": 0,
"unavailableReplicas": 0
}
},
{
"kind": "ImageStream",
"apiVersion": "v1",
"metadata": {
"name": "myservice",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "myservice"
}
},
"spec": {
"lookupPolicy": {
"local": false
},
"tags": [
{
"name": "latest",
"annotations": null,
"from": {
"kind": "DockerImage",
"name": "alpine"
},
"generation": null,
"importPolicy": {},
"referencePolicy": {
"type": ""
}
}
]
},
"status": {
"dockerImageRepository": ""
}
}
]
}