Compare commits

..

No commits in common. "pm-fix-formatting" and "main" have entirely different histories.

15 changed files with 120 additions and 844 deletions

View File

@ -29,7 +29,7 @@ jobs:
run: make bin
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: "kompose"
path: "kompose"

View File

@ -1,49 +0,0 @@
name: Release
on:
release:
types: [created]
env:
TERM: dumb
jobs:
build-and-release:
name: Build and Upload Release Assets
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v5
with:
go-version: ^1.21
id: go
- name: Check out code
uses: actions/checkout@v4
- name: Build Linux AMD64 binary
run: make cross
- name: Check binary permissions
run: |
cd bin
ls -la kompose-linux-amd64
- name: Get the version
id: vars
run: |
echo ::set-output name=tag::$(echo ${GITHUB_REF#refs/tags/})
- name: Upload binary to release assets
uses: https://gitea.com/cerc-io/action-gh-release@gitea-v2
with:
files: ./bin/kompose-linux-amd64
token: ${{ secrets.CICD_PUBLISH_TOKEN }}
- name: Publish binary as generic package
run: |
curl --user "${{ github.repository_owner }}:${{ secrets.CICD_PUBLISH_TOKEN }}" \
--upload-file ./bin/kompose-linux-amd64 \
"https://git.vdb.to/api/packages/${{ github.repository_owner }}/generic/kompose/${{ steps.vars.outputs.tag }}/kompose-linux-amd64"

View File

@ -2,7 +2,7 @@ name: Kompose CI
on:
push:
branches:
- main
- main
pull_request:
env:
# Avoid noisy outputs like "tput: No value for $TERM and no -T specified"
@ -27,23 +27,23 @@ jobs:
- name: Perform cross compile
if: ${{ matrix.cross_compile }}
run: make cross
# docs:
# name: Build docs and Coveralls integration
# runs-on: ubuntu-latest
# needs: test
# steps:
# - uses: actions/checkout@v4
# - uses: actions/setup-go@v5
# with:
# go-version: ^1.21
# - name: Install dyff
# run: go install github.com/homeport/dyff/cmd/dyff@v1.5.8
# - name: Create .coverprofile for each targeted directory by re:running tests
# run: make test
# - name: Collect all .coverprofile files and save it to one file gover.coverprofile
# run: gover
# - name: Send coverage
# run: goveralls -coverprofile=gover.coverprofile -service=github
# env:
# # As per https://github.com/mattn/goveralls#github-actions
# COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
docs:
name: Build docs and Coveralls integration
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ^1.21
- name: Install dyff
run: go install github.com/homeport/dyff/cmd/dyff@v1.5.8
- name: Create .coverprofile for each targeted directory by re:running tests
run: make test
- name: Collect all .coverprofile files and save it to one file gover.coverprofile
run: gover
- name: Send coverage
run: goveralls -coverprofile=gover.coverprofile -service=github
env:
# As per https://github.com/mattn/goveralls#github-actions
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -49,11 +49,11 @@ install:
.PHONY: cross
cross:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-linux-amd64" main.go
# GOOS=linux GOARCH=arm CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-linux-arm" main.go
# GOOS=linux GOARCH=arm64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-linux-arm64" main.go
# GOOS=windows GOARCH=amd64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-windows-amd64.exe" main.go
# GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-darwin-amd64" main.go
# GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-darwin-arm64" main.go
GOOS=linux GOARCH=arm CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-linux-arm" main.go
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-linux-arm64" main.go
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-windows-amd64.exe" main.go
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-darwin-amd64" main.go
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-darwin-arm64" main.go
.PHONY: clean
clean:

View File

@ -1 +1 @@
1.37.0-zenith-0.0.1
1.37.0

View File

@ -261,12 +261,7 @@ func Convert(opt kobject.ConvertOptions) ([]runtime.Object, error) {
}
// Print output
// Extract kubernetes transformer for PVC values if applicable
var k8sTransformer *kubernetes.Kubernetes
if k8s, ok := t.(*kubernetes.Kubernetes); ok {
k8sTransformer = k8s
}
err = kubernetes.PrintList(objects, opt, komposeObject, k8sTransformer)
err = kubernetes.PrintList(objects, opt)
if err != nil {
log.Fatalf(err.Error())
}

View File

@ -223,19 +223,16 @@ func (port *Ports) ID() string {
// Volumes holds the volume struct of container
type Volumes struct {
SvcName string // Service name to which volume is linked
MountPath string // Mountpath extracted from docker-compose file
VFrom string // denotes service name from which volume is coming
VolumeName string // name of volume if provided explicitly
Host string // host machine address
Container string // Mountpath
Mode string // access mode for volume
PVCName string // name of PVC
PVCSize string // PVC size
SelectorValue string // Value of the label selector
VolumeType string // Type of volume (e.g., "secret")
SecretName string // Name of Kubernetes Secret (if VolumeType is "secret")
Annotations map[string]string // Annotations to add to PVC (e.g., "helm.sh/resource-policy": "keep")
SvcName string // Service name to which volume is linked
MountPath string // Mountpath extracted from docker-compose file
VFrom string // denotes service name from which volume is coming
VolumeName string // name of volume if provided explicitly
Host string // host machine address
Container string // Mountpath
Mode string // access mode for volume
PVCName string // name of PVC
PVCSize string // PVC size
SelectorValue string // Value of the label selector
}
// Placement holds the placement struct of container

View File

@ -606,13 +606,7 @@ func dockerComposeToKomposeMapping(composeObject *types.Project) (kobject.Kompos
komposeObject.ServiceConfigs[normalizeServiceNames(name)] = serviceConfig
}
// Normalize volume names in the volumes map so lookups work with normalized names
normalizedVolumes := make(types.Volumes)
for volName, volConfig := range composeObject.Volumes {
normalizedVolumes[normalizeVolumes(volName)] = volConfig
}
handleVolume(&komposeObject, &normalizedVolumes)
handleVolume(&komposeObject, &composeObject.Volumes)
return komposeObject, nil
}
@ -845,15 +839,12 @@ func handleVolume(komposeObject *kobject.KomposeObject, volumes *types.Volumes)
errors.Wrap(err, "could not retrieve vvolume")
}
for volName, vol := range vols {
size, selector, volumeType, secretName, annotations := getVolumeLabels(vol.VolumeName, volumes)
if len(size) > 0 || len(selector) > 0 || len(volumeType) > 0 || len(secretName) > 0 || len(annotations) > 0 {
size, selector := getVolumeLabels(vol.VolumeName, volumes)
if len(size) > 0 || len(selector) > 0 {
// We can't assign value to struct field in map while iterating over it, so temporary variable `temp` is used here
var temp = vols[volName]
temp.PVCSize = size
temp.SelectorValue = selector
temp.VolumeType = volumeType
temp.SecretName = secretName
temp.Annotations = annotations
vols[volName] = temp
}
}
@ -958,36 +949,20 @@ func getVol(toFind kobject.Volumes, Vols []kobject.Volumes) (bool, kobject.Volum
return false, kobject.Volumes{}
}
func getVolumeLabels(name string, volumes *types.Volumes) (string, string, string, string, map[string]string) {
size, selector, volumeType, secretName := "", "", "", ""
annotations := make(map[string]string)
func getVolumeLabels(name string, volumes *types.Volumes) (string, string) {
size, selector := "", ""
if volume, ok := (*volumes)[name]; ok {
log.Debugf("Getting labels for volume %s, labels: %v", name, volume.Labels)
for key, value := range volume.Labels {
if key == "kompose.volume.size" {
size = value
} else if key == "kompose.volume.selector" {
selector = value
} else if key == "kompose.volume.type" {
volumeType = value
} else if key == "kompose.volume.secret-name" {
secretName = value
} else if strings.HasPrefix(key, "kompose.volume.annotations/") {
// Extract annotation key by removing prefix and replacing ~ with /
annotationKey := strings.TrimPrefix(key, "kompose.volume.annotations/")
annotationKey = strings.ReplaceAll(annotationKey, "~", "/")
annotations[annotationKey] = value
}
}
if volumeType != "" {
log.Infof("Volume %s has type %s, secret name: %s", name, volumeType, secretName)
}
} else {
log.Debugf("Volume %s not found in volumes map", name)
}
return size, selector, volumeType, secretName, annotations
return size, selector
}
// getGroupAdd will return group in int64 format

View File

@ -94,14 +94,6 @@ const (
LabelInitContainerImage = "kompose.init.containers.image"
// LabelInitContainerCommand defines commands
LabelInitContainerCommand = "kompose.init.containers.command"
// LabelInitContainerService defines service(s) to use as init containers (comma-separated)
LabelInitContainerService = "kompose.init.containers.service"
// LabelVolumeType defines the type of volume (e.g., "secret" for Kubernetes Secret volumes)
LabelVolumeType = "kompose.volume.type"
// LabelVolumeSecretName defines the name of the Kubernetes Secret to use
LabelVolumeSecretName = "kompose.volume.secret-name"
// LabelVolumeAnnotationsPrefix is the prefix for volume annotations (e.g., kompose.volume.annotations/helm.sh~resource-policy: keep)
LabelVolumeAnnotationsPrefix = "kompose.volume.annotations/"
// LabelHpaMinReplicas defines min pod replicas
LabelHpaMinReplicas = "kompose.hpa.replicas.min"
// LabelHpaMaxReplicas defines max pod replicas

View File

@ -82,175 +82,10 @@ type DeploymentMapping struct {
TargetDeploymentName string
}
// ServiceValues holds the extracted values for templating
type ServiceValues struct {
Image struct {
Repository string
Tag string
PullPolicy string
}
Env map[string]string
Replicas int `yaml:"replicas,omitempty"`
}
type PersistenceValues struct {
Size string
}
// splitImage splits "repo:tag" or "repo" into repository and tag
func splitImage(image string) (string, string) {
parts := strings.Split(image, ":")
if len(parts) == 2 {
return parts[0], parts[1]
}
// No tag specified, use "latest"
return image, "latest"
}
// unquoteHelmTemplates removes quotes around Helm template syntax that yaml.Marshal adds
func unquoteHelmTemplates(yamlBytes []byte) []byte {
yamlStr := string(yamlBytes)
lines := strings.Split(yamlStr, "\n")
for i, line := range lines {
// If line contains Helm template syntax {{...}}
if strings.Contains(line, "{{") && strings.Contains(line, "}}") {
// Remove outer quotes only - find the first and last quote on the line
// that enclose the entire value (after the colon for YAML)
if idx := strings.Index(line, ": "); idx != -1 {
prefix := line[:idx+2] // Keep "key: "
value := line[idx+2:] // Get the value part
// Remove surrounding quotes from value if present
if (strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`)) ||
(strings.HasPrefix(value, `'`) && strings.HasSuffix(value, `'`)) {
value = value[1 : len(value)-1]
}
lines[i] = prefix + value
}
}
}
return []byte(strings.Join(lines, "\n"))
}
// templatePVCStorage replaces PVC storage values with Helm templates
// This post-processes YAML because resource.Quantity validates input and rejects template strings
func templatePVCStorage(yamlBytes []byte, persistenceValues map[string]PersistenceValues) []byte {
yamlStr := string(yamlBytes)
// Only process PersistentVolumeClaim or StatefulSet with volumeClaimTemplates
if !strings.Contains(yamlStr, "kind: PersistentVolumeClaim") &&
!(strings.Contains(yamlStr, "kind: StatefulSet") && strings.Contains(yamlStr, "volumeClaimTemplates:")) {
return yamlBytes
}
lines := strings.Split(yamlStr, "\n")
var pvcName string
inVolumeClaimTemplates := false
inMetadata := false
// Extract PVC name from metadata
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.Contains(trimmed, "volumeClaimTemplates:") {
inVolumeClaimTemplates = true
} else if (trimmed == "metadata:" || trimmed == "- metadata:") && (inVolumeClaimTemplates || !strings.Contains(yamlStr, "volumeClaimTemplates:")) {
inMetadata = true
} else if inMetadata && strings.HasPrefix(trimmed, "name:") {
if parts := strings.SplitN(trimmed, ":", 2); len(parts) == 2 {
pvcName = strings.TrimSpace(parts[1])
break
}
}
}
// Template the storage value if PVC name is in persistence values
if pvcName != "" {
if _, exists := persistenceValues[pvcName]; exists {
templateStr := "{{ index .Values \"persistence\" \"" + pvcName + "\" \"size\" }}"
for i, line := range lines {
if strings.Contains(line, "storage:") {
prefix := line[:strings.Index(line, ":")+2]
lines[i] = prefix + templateStr
break
}
}
yamlStr = strings.Join(lines, "\n")
}
}
return []byte(yamlStr)
}
// templateReplicas replaces hardcoded replica values with Helm templates
// This post-processes YAML because Replicas is *int32 and rejects template strings
func templateReplicas(yamlBytes []byte, serviceName string) []byte {
yamlStr := string(yamlBytes)
// Only process Deployment or StatefulSet (DaemonSet doesn't use replicas)
if !strings.Contains(yamlStr, "kind: Deployment") && !strings.Contains(yamlStr, "kind: StatefulSet") {
return yamlBytes
}
lines := strings.Split(yamlStr, "\n")
templateStr := "{{ " + helmValuesPath(serviceName, "replicas") + " | default 1 }}"
for i, line := range lines {
if strings.HasPrefix(strings.TrimSpace(line), "replicas:") {
indentEnd := strings.Index(line, "replicas:")
lines[i] = line[:indentEnd] + "replicas: " + templateStr
break
}
}
return []byte(strings.Join(lines, "\n"))
}
// extractValuesFromKomposeObject extracts service values from KomposeObject for values.yaml
// Note: PVC values are extracted during transformation and stored in Kubernetes.PersistenceValues
func extractValuesFromKomposeObject(komposeObject kobject.KomposeObject) (map[string]ServiceValues, map[string]PersistenceValues) {
serviceValues := make(map[string]ServiceValues)
for serviceName, service := range komposeObject.ServiceConfigs {
svcValues := ServiceValues{}
// Extract image
repo, tag := splitImage(service.Image)
svcValues.Image.Repository = repo
svcValues.Image.Tag = tag
// Extract pull policy
pullPolicy := service.ImagePullPolicy
if pullPolicy == "" {
pullPolicy = "IfNotPresent"
}
svcValues.Image.PullPolicy = pullPolicy
// Extract env vars
svcValues.Env = make(map[string]string)
for _, envVar := range service.Environment {
svcValues.Env[envVar.Name] = envVar.Value
}
// Extract replicas (default to 1 if not set)
if service.Replicas > 0 {
svcValues.Replicas = service.Replicas
} else {
svcValues.Replicas = 1
}
serviceValues[serviceName] = svcValues
}
return serviceValues, nil
}
/**
* Generate Helm Chart configuration
*/
func generateHelm(dirName string, serviceValues map[string]ServiceValues, persistenceValues map[string]PersistenceValues) error {
func generateHelm(dirName string) error {
type ChartDetails struct {
Name string
}
@ -306,84 +141,10 @@ home:
return err
}
/* Create the values.yaml file */
if len(serviceValues) > 0 || len(persistenceValues) > 0 {
valuesYAML, err := generateValuesYAML(serviceValues, persistenceValues)
if err != nil {
return errors.Wrap(err, "Failed to generate values.yaml")
}
err = os.WriteFile(dirName+string(os.PathSeparator)+"values.yaml", valuesYAML, 0644)
if err != nil {
return err
}
}
log.Infof("chart created in %q\n", dirName+string(os.PathSeparator))
return nil
}
// generateValuesYAML creates values.yaml content from extracted values
func generateValuesYAML(serviceValues map[string]ServiceValues, persistenceValues map[string]PersistenceValues) ([]byte, error) {
// Build hierarchical structure: serviceName -> image -> {repository, tag, pullPolicy}
valuesMap := make(map[string]interface{})
// Sort service names for consistent output
serviceNames := make([]string, 0, len(serviceValues))
for name := range serviceValues {
serviceNames = append(serviceNames, name)
}
sort.Strings(serviceNames)
for _, serviceName := range serviceNames {
svcValues := serviceValues[serviceName]
serviceMap := map[string]interface{}{
"image": map[string]string{
"repository": svcValues.Image.Repository,
"tag": svcValues.Image.Tag,
"pullPolicy": svcValues.Image.PullPolicy,
},
}
// Add env vars if present
if len(svcValues.Env) > 0 {
serviceMap["env"] = svcValues.Env
}
// Add replicas
serviceMap["replicas"] = svcValues.Replicas
valuesMap[serviceName] = serviceMap
}
// Add persistence values if present
if len(persistenceValues) > 0 {
// Sort PVC names for consistent output
pvcNames := make([]string, 0, len(persistenceValues))
for name := range persistenceValues {
pvcNames = append(pvcNames, name)
}
sort.Strings(pvcNames)
persistenceMap := make(map[string]interface{})
for _, pvcName := range pvcNames {
pvcValues := persistenceValues[pvcName]
persistenceMap[pvcName] = map[string]string{
"size": pvcValues.Size,
}
}
valuesMap["persistence"] = persistenceMap
}
// Use marshalWithIndent for consistent 2-space indentation
yamlBytes, err := marshalWithIndent(valuesMap, 2)
if err != nil {
return nil, err
}
return yamlBytes, nil
}
// Check if given path is a directory
func isDir(name string) (bool, error) {
// Open file to get stat later
@ -422,7 +183,7 @@ func getDirName(opt kobject.ConvertOptions) string {
}
// PrintList will take the data converted and decide on the commandline attributes given
func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObject kobject.KomposeObject, k8sTransformer *Kubernetes) error {
func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
var f *os.File
dirName := getDirName(opt)
log.Debugf("Target Dir: %s", dirName)
@ -454,21 +215,6 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObje
}
var files []string
var serviceValues map[string]ServiceValues
var persistenceValues map[string]PersistenceValues
// Extract values from KomposeObject for values.yaml
if opt.CreateChart {
serviceValues, _ = extractValuesFromKomposeObject(komposeObject)
// Get persistence values from transformer (populated during PVC creation)
if k8sTransformer != nil && k8sTransformer.PersistenceValues != nil {
persistenceValues = make(map[string]PersistenceValues)
for name, size := range k8sTransformer.PersistenceValues {
persistenceValues[name] = PersistenceValues{Size: size}
}
}
}
// if asked to print to stdout or to put in single file
// we will create a list
if opt.ToStdout || f != nil {
@ -486,13 +232,6 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObje
if err != nil {
return fmt.Errorf("error in marshalling the List: %v", err)
}
// Unquote Helm templates if generating chart
if opt.CreateChart {
data = unquoteHelmTemplates(data)
data = templatePVCStorage(data, persistenceValues)
}
// this part add --- which unifies the file
data = []byte(fmt.Sprintf("---\n%s", data))
printVal, err := transformer.Print("", dirName, "", data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider)
@ -526,7 +265,6 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObje
var typeMeta metav1.TypeMeta
var objectMeta metav1.ObjectMeta
// Get object metadata first for templating
if us, ok := v.(*unstructured.Unstructured); ok {
typeMeta = metav1.TypeMeta{
Kind: us.GetKind(),
@ -537,15 +275,13 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObje
}
} else {
val := reflect.ValueOf(v).Elem()
// Use reflect to access TypeMeta struct inside runtime.Object.
// cast it to correct type - metav1.TypeMeta
typeMeta = val.FieldByName("TypeMeta").Interface().(metav1.TypeMeta)
objectMeta = val.FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta)
}
// Unquote Helm templates if generating chart
if opt.CreateChart {
data = unquoteHelmTemplates(data)
data = templatePVCStorage(data, persistenceValues)
data = templateReplicas(data, objectMeta.Name)
// Use reflect to access ObjectMeta struct inside runtime.Object.
// cast it to correct type - api.ObjectMeta
objectMeta = val.FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta)
}
file, err = transformer.Print(objectMeta.Name, finalDirName, strings.ToLower(typeMeta.Kind), data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider)
@ -557,7 +293,7 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObje
}
}
if opt.CreateChart {
err = generateHelm(dirName, serviceValues, persistenceValues)
err = generateHelm(dirName)
if err != nil {
return errors.Wrap(err, "generateHelm failed")
}
@ -620,24 +356,6 @@ func removeEmptyInterfaces(obj interface{}) interface{} {
}
// Convert JSON to YAML.
// setLiteralStyleForMultilineStrings walks through a yaml.Node tree and sets
// multiline strings to use literal style (|) for better readability
func setLiteralStyleForMultilineStrings(node *yaml.Node) {
if node == nil {
return
}
// If this is a scalar string node with newlines, use literal style
if node.Kind == yaml.ScalarNode && node.Tag == "!!str" && strings.Contains(node.Value, "\n") {
node.Style = yaml.LiteralStyle // Use | for multiline strings
}
// Recursively process child nodes
for _, child := range node.Content {
setLiteralStyleForMultilineStrings(child)
}
}
func jsonToYaml(j []byte, spaces int) ([]byte, error) {
// Convert the JSON to an object.
var jsonObj interface{}
@ -651,20 +369,10 @@ func jsonToYaml(j []byte, spaces int) ([]byte, error) {
return nil, err
}
jsonObj = removeEmptyInterfaces(jsonObj)
// Create a yaml.Node to have control over string styles
var node yaml.Node
if err := node.Encode(jsonObj); err != nil {
return nil, err
}
// Set literal style for multiline strings (e.g., ConfigMap data)
setLiteralStyleForMultilineStrings(&node)
var b bytes.Buffer
encoder := yaml.NewEncoder(&b)
encoder.SetIndent(spaces)
if err := encoder.Encode(&node); err != nil {
if err := encoder.Encode(jsonObj); err != nil {
return nil, err
}
return b.Bytes(), nil
@ -838,7 +546,7 @@ func (k *Kubernetes) UpdateKubernetesObjectsMultipleContainers(name string, serv
}
// UpdateKubernetesObjects loads configurations to k8s objects
func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object, komposeObject *kobject.KomposeObject) error {
func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object) error {
// Configure the environment variables.
envs, envsFrom, err := ConfigEnvs(service, opt)
if err != nil {
@ -846,7 +554,7 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
}
// Configure the container volumes.
volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(name, service, opt)
volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(name, service)
if err != nil {
return errors.Wrap(err, "k.ConfigVolumes failed")
}
@ -857,15 +565,7 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
volumesMount = append(volumesMount, TmpVolumesMount...)
}
// Check if service uses StatefulSet controller (either from opt or from label)
isStatefulSet := opt.Controller == StatefulStateController
if !isStatefulSet {
if controllerType, ok := service.Labels[compose.LabelControllerType]; ok {
isStatefulSet = (controllerType == StatefulStateController)
}
}
if pvc != nil && !isStatefulSet {
if pvc != nil && opt.Controller != StatefulStateController {
// Looping on the slice pvc instead of `*objects = append(*objects, pvc...)`
// because the type of objects and pvc is different, but when doing append
// one element at a time it gets converted to runtime.Object for objects slice
@ -886,102 +586,6 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
// Configure annotations
annotations := transformer.ConfigAnnotations(service)
// Process init container services if specified
var initContainers []api.Container
if initServicesStr, ok := service.Labels[compose.LabelInitContainerService]; ok && initServicesStr != "" && komposeObject != nil {
initServiceNames := strings.Split(initServicesStr, ",")
for _, initSvcName := range initServiceNames {
initSvcName = strings.TrimSpace(initSvcName)
if initSvcName == "" {
continue
}
// Find the init container service in the kompose object
initService, exists := komposeObject.ServiceConfigs[initSvcName]
if !exists {
log.Warnf("Init container service %s not found for service %s", initSvcName, name)
continue
}
log.Infof("Adding init container %s to service %s", initSvcName, name)
// Configure init container environment variables
initEnvs, initEnvsFrom, err := ConfigEnvs(initService, opt)
if err != nil {
return errors.Wrapf(err, "Unable to load env variables for init container %s", initSvcName)
}
// Configure init container volumes
initVolumeMounts, initVolumes, initPvc, initCms, err := k.ConfigVolumes(initSvcName, initService, opt)
if err != nil {
return errors.Wrapf(err, "k.ConfigVolumes failed for init container %s", initSvcName)
}
// Add init container volumes to the main volumes list (deduplicate by name)
existingVolumeNames := make(map[string]bool)
for _, v := range volumes {
existingVolumeNames[v.Name] = true
}
for _, initVol := range initVolumes {
if !existingVolumeNames[initVol.Name] {
volumes = append(volumes, initVol)
existingVolumeNames[initVol.Name] = true
}
}
// Add init container PVCs and ConfigMaps to objects (deduplicate by name)
// Build a set of existing object names
existingObjectNames := make(map[string]bool)
for _, obj := range *objects {
switch o := obj.(type) {
case *api.PersistentVolumeClaim:
existingObjectNames[o.Name] = true
case *api.ConfigMap:
existingObjectNames[o.Name] = true
}
}
// Skip PVCs for init containers if parent service is StatefulSet
if initPvc != nil && !isStatefulSet {
for _, p := range initPvc {
if !existingObjectNames[p.Name] {
*objects = append(*objects, p)
existingObjectNames[p.Name] = true
}
}
}
for _, c := range initCms {
if !existingObjectNames[c.Name] {
*objects = append(*objects, c)
existingObjectNames[c.Name] = true
}
}
// Get init container image
initImage := initService.Image
if initImage == "" {
initImage = initSvcName
}
if opt.CreateChart {
initImage = "{{ " + helmValuesPath(initSvcName, "image", "repository") + " }}:{{ " + helmValuesPath(initSvcName, "image", "tag") + " }}"
}
// Create init container spec
initContainer := api.Container{
Name: FormatContainerName(initSvcName),
Image: initImage,
Command: initService.Command,
Args: GetContainerArgs(initService),
Env: initEnvs,
EnvFrom: initEnvsFrom,
VolumeMounts: initVolumeMounts,
WorkingDir: initService.WorkingDir,
}
initContainers = append(initContainers, initContainer)
}
}
// fillTemplate fills the pod template with the value calculated from config
fillTemplate := func(template *api.PodTemplateSpec) error {
template.Spec.Containers[0].Name = GetContainerName(service)
@ -1118,11 +722,6 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
template.Spec.ServiceAccountName = serviceAccountName
}
fillInitContainers(template, service)
// Add init containers from referenced services
if len(initContainers) > 0 {
template.Spec.InitContainers = append(template.Spec.InitContainers, initContainers...)
}
return nil
}
@ -1153,26 +752,8 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
persistentVolumeClaims[i] = *persistentVolumeClaim
persistentVolumeClaims[i].APIVersion = ""
persistentVolumeClaims[i].Kind = ""
// Store PVC size for helm chart values (for volumeClaimTemplates)
if opt.CreateChart {
pvcName := persistentVolumeClaim.Name
if storageQty, ok := persistentVolumeClaim.Spec.Resources.Requests[api.ResourceStorage]; ok {
k.PersistenceValues[pvcName] = storageQty.String()
}
}
}
objType.Spec.VolumeClaimTemplates = persistentVolumeClaims
// Remove PVC volumes from pod spec since they come from volumeClaimTemplates
// Keep only non-PVC volumes (ConfigMaps, Secrets, EmptyDir, HostPath, etc.)
var filteredVolumes []api.Volume
for _, vol := range objType.Spec.Template.Spec.Volumes {
if vol.PersistentVolumeClaim == nil {
filteredVolumes = append(filteredVolumes, vol)
}
}
objType.Spec.Template.Spec.Volumes = filteredVolumes
}
}
}

View File

@ -55,26 +55,13 @@ import (
type Kubernetes struct {
// the user provided options from the command line
Opt kobject.ConvertOptions
// PersistenceValues stores PVC sizes for helm chart values.yaml
PersistenceValues map[string]string
}
// helmValuesPath generates a Helm template path using index notation
// This works for all service names including those with hyphens
func helmValuesPath(parts ...string) string {
quotedParts := make([]string, len(parts)+1)
quotedParts[0] = ".Values"
for i, part := range parts {
quotedParts[i+1] = `"` + part + `"`
}
return "index " + strings.Join(quotedParts, " ")
}
// 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": {}, "secret": {}}
var ValidVolumeSet = map[string]struct{}{"emptyDir": {}, "hostPath": {}, "configMap": {}, "persistentVolumeClaim": {}}
const (
// DeploymentController is controller type for Deployment
@ -83,8 +70,6 @@ const (
DaemonSetController = "daemonset"
// StatefulStateController is controller type for StatefulSet
StatefulStateController = "statefulset"
// JobController is controller type for Job
JobController = "job"
)
// CheckUnsupportedKey checks if given komposeObject contains
@ -124,17 +109,10 @@ func (k *Kubernetes) CheckUnsupportedKey(komposeObject *kobject.KomposeObject, u
}
// InitPodSpec creates the pod specification
func (k *Kubernetes) InitPodSpec(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) api.PodSpec {
image := service.Image
func (k *Kubernetes) InitPodSpec(name string, image string, pullSecret string) api.PodSpec {
if image == "" {
image = name
}
// Inject Helm template for chart generation
if opt.CreateChart {
image = "{{ " + helmValuesPath(name, "image", "repository") + " }}:{{ " + helmValuesPath(name, "image", "tag") + " }}"
}
pod := api.PodSpec{
Containers: []api.Container{
{
@ -143,11 +121,10 @@ func (k *Kubernetes) InitPodSpec(name string, service kobject.ServiceConfig, opt
},
},
}
if service.ImagePullSecret != "" {
if pullSecret != "" {
pod.ImagePullSecrets = []api.LocalObjectReference{
{
Name: service.ImagePullSecret,
Name: pullSecret,
},
}
}
@ -155,7 +132,7 @@ func (k *Kubernetes) InitPodSpec(name string, service kobject.ServiceConfig, opt
}
// InitPodSpecWithConfigMap creates the pod specification
func (k *Kubernetes) InitPodSpecWithConfigMap(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) api.PodSpec {
func (k *Kubernetes) InitPodSpecWithConfigMap(name string, image string, service kobject.ServiceConfig) api.PodSpec {
var volumeMounts []api.VolumeMount
var volumes []api.Volume
@ -200,16 +177,6 @@ func (k *Kubernetes) InitPodSpecWithConfigMap(name string, service kobject.Servi
volumes = append(volumes, cmVol)
}
image := service.Image
if image == "" {
image = name
}
// Inject Helm template for chart generation
if opt.CreateChart {
image = "{{ " + helmValuesPath(name, "image", "repository") + " }}:{{ " + helmValuesPath(name, "image", "tag") + " }}"
}
pod := api.PodSpec{
Containers: []api.Container{
{
@ -362,10 +329,6 @@ func (k *Kubernetes) IntiConfigMapFromFileOrDir(name, cmName, filePath string, s
configMap.Annotations = map[string]string{
"use-subpath": "true",
}
// Check if the file is executable and store that info
if mode&0111 != 0 { // Check if any execute bit is set
configMap.Annotations["executable"] = "true"
}
}
return configMap, nil
@ -449,12 +412,12 @@ func (k *Kubernetes) InitConfigMapFromFile(name string, service kobject.ServiceC
}
// InitD initializes Kubernetes Deployment object
func (k *Kubernetes) InitD(name string, service kobject.ServiceConfig, replicas int, opt kobject.ConvertOptions) *appsv1.Deployment {
func (k *Kubernetes) InitD(name string, service kobject.ServiceConfig, replicas int) *appsv1.Deployment {
var podSpec api.PodSpec
if len(service.Configs) > 0 {
podSpec = k.InitPodSpecWithConfigMap(name, service, opt)
podSpec = k.InitPodSpecWithConfigMap(name, service.Image, service)
} else {
podSpec = k.InitPodSpec(name, service, opt)
podSpec = k.InitPodSpec(name, service.Image, service.ImagePullSecret)
}
rp := int32(replicas)
@ -505,7 +468,7 @@ func (k *Kubernetes) InitD(name string, service kobject.ServiceConfig, replicas
}
// InitDS initializes Kubernetes DaemonSet object
func (k *Kubernetes) InitDS(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) *appsv1.DaemonSet {
func (k *Kubernetes) InitDS(name string, service kobject.ServiceConfig) *appsv1.DaemonSet {
ds := &appsv1.DaemonSet{
TypeMeta: metav1.TypeMeta{
Kind: "DaemonSet",
@ -520,66 +483,20 @@ func (k *Kubernetes) InitDS(name string, service kobject.ServiceConfig, opt kobj
MatchLabels: transformer.ConfigLabels(name),
},
Template: api.PodTemplateSpec{
Spec: k.InitPodSpec(name, service, opt),
Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret),
},
},
}
return ds
}
// InitJob initializes Kubernetes Job object
func (k *Kubernetes) InitJob(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) *batchv1.Job {
var podSpec api.PodSpec
if len(service.Configs) > 0 {
podSpec = k.InitPodSpecWithConfigMap(name, service, opt)
} else {
podSpec = k.InitPodSpec(name, service, opt)
}
// Jobs need RestartPolicy set (default is Always which is invalid for Jobs)
// Use Never for jobs unless explicitly set
if podSpec.RestartPolicy == "" || podSpec.RestartPolicy == api.RestartPolicyAlways {
podSpec.RestartPolicy = api.RestartPolicyNever
}
job := &batchv1.Job{
TypeMeta: metav1.TypeMeta{
Kind: "Job",
APIVersion: "batch/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: transformer.ConfigAllLabels(name, &service),
},
Spec: batchv1.JobSpec{
Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: transformer.ConfigLabels(name),
},
Spec: podSpec,
},
},
}
// Set backoffLimit from label if specified
if backoffLimit, ok := service.Labels["kompose.job.backoff-limit"]; ok {
limit, err := strconv.ParseInt(backoffLimit, 10, 32)
if err == nil {
limit32 := int32(limit)
job.Spec.BackoffLimit = &limit32
}
}
return job
}
// InitSS method initialize a stateful set
func (k *Kubernetes) InitSS(name string, service kobject.ServiceConfig, replicas int, opt kobject.ConvertOptions) *appsv1.StatefulSet {
func (k *Kubernetes) InitSS(name string, service kobject.ServiceConfig, replicas int) *appsv1.StatefulSet {
var podSpec api.PodSpec
if len(service.Configs) > 0 {
podSpec = k.InitPodSpecWithConfigMap(name, service, opt)
podSpec = k.InitPodSpecWithConfigMap(name, service.Image, service)
} else {
podSpec = k.InitPodSpec(name, service, opt)
podSpec = k.InitPodSpec(name, service.Image, service.ImagePullSecret)
}
rp := int32(replicas)
ds := &appsv1.StatefulSet{
@ -606,7 +523,7 @@ func (k *Kubernetes) InitSS(name string, service kobject.ServiceConfig, replicas
}
// InitCJ initializes Kubernetes CronJob object
func (k *Kubernetes) InitCJ(name string, service kobject.ServiceConfig, schedule string, concurrencyPolicy batchv1.ConcurrencyPolicy, backoffLimit *int32, opt kobject.ConvertOptions) *batchv1.CronJob {
func (k *Kubernetes) InitCJ(name string, service kobject.ServiceConfig, schedule string, concurrencyPolicy batchv1.ConcurrencyPolicy, backoffLimit *int32) *batchv1.CronJob {
cj := &batchv1.CronJob{
TypeMeta: metav1.TypeMeta{
Kind: "CronJob",
@ -623,7 +540,7 @@ func (k *Kubernetes) InitCJ(name string, service kobject.ServiceConfig, schedule
Spec: batchv1.JobSpec{
BackoffLimit: backoffLimit,
Template: api.PodTemplateSpec{
Spec: k.InitPodSpec(name, service, opt),
Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret),
},
},
},
@ -738,12 +655,8 @@ func (k *Kubernetes) CreateSecrets(komposeObject kobject.KomposeObject) ([]*api.
}
// CreatePVC initializes PersistentVolumeClaim
func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorValue string, storageClassName string, annotations map[string]string, opt kobject.ConvertOptions) (*api.PersistentVolumeClaim, error) {
var volSize resource.Quantity
var err error
// Parse the size value
volSize, err = resource.ParseQuantity(size)
func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorValue string, storageClassName string) (*api.PersistentVolumeClaim, error) {
volSize, err := resource.ParseQuantity(size)
if err != nil {
return nil, errors.Wrap(err, "resource.ParseQuantity failed, Error parsing size")
}
@ -766,16 +679,6 @@ func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorVa
},
}
// Add annotations if specified
if len(annotations) > 0 {
if pvc.ObjectMeta.Annotations == nil {
pvc.ObjectMeta.Annotations = make(map[string]string)
}
for key, value := range annotations {
pvc.ObjectMeta.Annotations[key] = value
}
}
if len(selectorValue) > 0 {
pvc.Spec.Selector = &metav1.LabelSelector{
MatchLabels: transformer.ConfigLabels(selectorValue),
@ -1064,11 +967,7 @@ func (k *Kubernetes) getSecretPathsLegacy(secretConfig types.ServiceSecretConfig
}
// ConfigVolumes configure the container volumes.
func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]api.VolumeMount, []api.Volume, []*api.PersistentVolumeClaim, []*api.ConfigMap, error) {
// Store PVC info for helm chart values
if opt.CreateChart && k.PersistenceValues == nil {
k.PersistenceValues = make(map[string]string)
}
func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume, []*api.PersistentVolumeClaim, []*api.ConfigMap, error) {
volumeMounts := []api.VolumeMount{}
volumes := []api.Volume{}
var PVCs []*api.PersistentVolumeClaim
@ -1090,15 +989,13 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig, o
}
// Override volume type if specified in service labels.
var useSecret bool
if vt, ok := service.Labels[compose.LabelVolumeType]; ok {
if vt, ok := service.Labels["kompose.volume.type"]; ok {
if _, okk := ValidVolumeSet[vt]; !okk {
return nil, nil, nil, nil, fmt.Errorf("invalid volume type %s specified in label '%s' in service %s", vt, compose.LabelVolumeType, service.Name)
return nil, nil, nil, nil, fmt.Errorf("invalid volume type %s specified in label 'kompose.volume.type' in service %s", vt, service.Name)
}
useEmptyVolumes = vt == "emptyDir"
useHostPath = vt == "hostPath"
useConfigMap = vt == "configMap"
useSecret = vt == "secret"
}
// config volumes from secret if present
@ -1112,52 +1009,23 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig, o
for _, volume := range service.Volumes {
// check if ro/rw mode is defined, default rw
readonly := len(volume.Mode) > 0 && (volume.Mode == "ro" || volume.Mode == "rox")
// Reset volume type flags for each volume (service-level flags don't apply to individual volumes)
volumeUseSecret := useSecret
volumeUseConfigMap := useConfigMap
volumeUseHostPath := useHostPath
volumeUseEmpty := useEmptyVolumes
// Check volume type from volume labels first, then fall back to file/path detection
if volume.VolumeType == "secret" {
// Explicit secret volume from volume labels
volumeUseSecret = true
volumeUseConfigMap = false
volumeUseHostPath = false
volumeUseEmpty = false
} else if volume.Host != "" {
// Bind mount: detect if it's a config file, otherwise treat as hostPath
volumeUseConfigMap, _, skip = isConfigFile(volume.Host)
if skip {
log.Warnf("Skip file in path %s ", volume.Host)
continue
}
if volumeUseConfigMap {
// It's a config file, use ConfigMap
volumeUseSecret = false
volumeUseHostPath = false
volumeUseEmpty = false
} else {
// It's a regular bind mount, use hostPath
volumeUseConfigMap = false
volumeUseSecret = false
volumeUseHostPath = true
volumeUseEmpty = false
}
} else {
// Named volume with no host path
// Keep volumeUseSecret if set, otherwise will fall through to PVC
volumeUseConfigMap = false
volumeUseHostPath = false
volumeUseEmpty = false
mountHost := volume.Host
if mountHost == "" {
mountHost = volume.MountPath
}
// return useconfigmap and readonly,
// not used asigned readonly because dont break e2e
useConfigMap, _, skip = isConfigFile(mountHost)
if skip {
log.Warnf("Skip file in path %s ", volume.Host)
continue
}
if volume.VolumeName == "" {
if volumeUseEmpty {
if useEmptyVolumes {
volumeName = strings.Replace(volume.PVCName, "claim", "empty", 1)
} else if volumeUseHostPath {
} else if useHostPath {
volumeName = strings.Replace(volume.PVCName, "claim", "hostpath", 1)
} else if volumeUseConfigMap {
} else if useConfigMap {
volumeName = strings.Replace(volume.PVCName, "claim", "cm", 1)
} else {
volumeName = volume.PVCName
@ -1179,15 +1047,15 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig, o
// For PVC we will also create a PVC object and add to list
var volsource *api.VolumeSource
if volumeUseEmpty {
if useEmptyVolumes {
volsource = k.ConfigEmptyVolumeSource("volume")
} else if volumeUseHostPath {
} else if useHostPath {
source, err := k.ConfigHostPathVolumeSource(volume.Host)
if err != nil {
return nil, nil, nil, nil, errors.Wrap(err, "k.ConfigHostPathVolumeSource failed")
}
volsource = source
} else if volumeUseConfigMap {
} else if useConfigMap {
log.Debugf("Use configmap volume")
cm, err := k.IntiConfigMapFromFileOrDir(name, volumeName, volume.Host, service)
if err != nil {
@ -1199,25 +1067,6 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig, o
if useSubPathMount(cm) {
volMount.SubPath = volsource.ConfigMap.Items[0].Path
}
} else if volumeUseSecret {
// Get secret name from volume config or service label, or default to volume name
secretName := volume.SecretName
if secretName == "" {
secretName = service.Labels[compose.LabelVolumeSecretName]
}
if secretName == "" {
secretName = volumeName
}
log.Infof("Creating Secret volume %s with secret %s", volumeName, secretName)
// Create secret volume source (all secrets are optional by default)
optional := true
volsource = &api.VolumeSource{
Secret: &api.SecretVolumeSource{
SecretName: secretName,
Optional: &optional,
},
}
} else {
volsource = k.ConfigPVCVolumeSource(volumeName, readonly)
if volume.VFrom == "" {
@ -1238,17 +1087,12 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig, o
}
}
createdPVC, err := k.CreatePVC(volumeName, volume.Mode, defaultSize, volume.SelectorValue, storageClassName, volume.Annotations, opt)
createdPVC, err := k.CreatePVC(volumeName, volume.Mode, defaultSize, volume.SelectorValue, storageClassName)
if err != nil {
return nil, nil, nil, nil, errors.Wrap(err, "k.CreatePVC failed")
}
// Store PVC size for helm chart values
if opt.CreateChart {
k.PersistenceValues[volumeName] = defaultSize
}
PVCs = append(PVCs, createdPVC)
}
}
@ -1292,13 +1136,6 @@ func (k *Kubernetes) ConfigEmptyVolumeSource(key string) *api.VolumeSource {
func (k *Kubernetes) ConfigConfigMapVolumeSource(cmName string, targetPath string, cm *api.ConfigMap) *api.VolumeSource {
s := api.ConfigMapVolumeSource{}
s.Name = cmName
// Set default mode to 0755 if the file is executable
if cm.Annotations != nil && cm.Annotations["executable"] == "true" {
mode := int32(0755)
s.DefaultMode = &mode
}
if useSubPathMount(cm) {
var keys []string
for k := range cm.Data {
@ -1388,18 +1225,12 @@ func ConfigEnvs(service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]ap
// Load up the environment variables
for _, v := range service.Environment {
if !keysFromEnvFile[v.Name] {
value := v.Value
if opt.CreateChart {
// Inject Helm template syntax for chart generation
value = "{{ " + helmValuesPath(service.Name, "env", v.Name) + " | quote }}"
} else {
if strings.Contains(value, "run/secrets") {
value = FormatResourceName(value)
}
if strings.Contains(v.Value, "run/secrets") {
v.Value = FormatResourceName(v.Value)
}
envs = append(envs, api.EnvVar{
Name: v.Name,
Value: value,
Value: v.Value,
})
}
}
@ -1516,19 +1347,15 @@ func (k *Kubernetes) CreateWorkloadAndConfigMapObjects(name string, service kobj
}
if opt.CreateD || opt.Controller == DeploymentController {
objects = append(objects, k.InitD(name, service, replica, opt))
objects = append(objects, k.InitD(name, service, replica))
}
if opt.CreateDS || opt.Controller == DaemonSetController {
objects = append(objects, k.InitDS(name, service, opt))
objects = append(objects, k.InitDS(name, service))
}
if opt.Controller == StatefulStateController {
objects = append(objects, k.InitSS(name, service, replica, opt))
}
if opt.Controller == JobController {
objects = append(objects, k.InitJob(name, service, opt))
objects = append(objects, k.InitSS(name, service, replica))
}
envConfigMaps := k.PargeEnvFiletoConfigMaps(name, service, opt)
@ -1565,7 +1392,7 @@ func (k *Kubernetes) createConfigMapFromComposeConfig(name string, service kobje
}
// InitPod initializes Kubernetes Pod object
func (k *Kubernetes) InitPod(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) *api.Pod {
func (k *Kubernetes) InitPod(name string, service kobject.ServiceConfig) *api.Pod {
pod := api.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
@ -1576,7 +1403,7 @@ func (k *Kubernetes) InitPod(name string, service kobject.ServiceConfig, opt kob
Labels: transformer.ConfigLabels(name),
Annotations: transformer.ConfigAnnotations(service),
},
Spec: k.InitPodSpec(name, service, opt),
Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret),
}
return &pod
}
@ -1786,7 +1613,7 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
k.configKubeServiceAndIngressForService(service, groupName, &objects)
// Configure the container volumes.
volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(groupName, service, opt)
volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(groupName, service)
if err != nil {
return nil, errors.Wrap(err, "k.ConfigVolumes failed")
}
@ -1844,22 +1671,6 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
allobjects = append(allobjects, objects...)
}
}
// Track services used as init containers (to skip creating deployments for them)
initContainerServices := make(map[string]bool)
for _, service := range komposeObject.ServiceConfigs {
if initServicesStr, ok := service.Labels[compose.LabelInitContainerService]; ok && initServicesStr != "" {
// Parse comma-separated list of init container service names
initServices := strings.Split(initServicesStr, ",")
for _, initSvc := range initServices {
initSvc = strings.TrimSpace(initSvc)
if initSvc != "" {
initContainerServices[initSvc] = true
log.Infof("Service %s will be used as init container for service %s", initSvc, service.Name)
}
}
}
}
sortedKeys := SortedKeys(komposeObject.ServiceConfigs)
for _, name := range sortedKeys {
service := komposeObject.ServiceConfigs[name]
@ -1869,12 +1680,6 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
continue
}
// Skip services that are used as init containers for other services
if initContainerServices[name] {
log.Infof("Skipping deployment creation for service %s (used as init container)", name)
continue
}
var objects []runtime.Object
service.WithKomposeAnnotation = opt.WithKomposeAnnotation
@ -1884,16 +1689,13 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
}
// Generate pod or cronjob and configmap objects
// Check if service has explicit controller type label
_, hasControllerLabel := service.Labels[compose.LabelControllerType]
if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() && !hasControllerLabel {
if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() {
if service.CronJobSchedule != "" {
log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart)
cronJob := k.InitCJ(name, service, service.CronJobSchedule, service.CronJobConcurrencyPolicy, service.CronJobBackoffLimit, opt)
cronJob := k.InitCJ(name, service, service.CronJobSchedule, service.CronJobConcurrencyPolicy, service.CronJobBackoffLimit)
objects = append(objects, cronJob)
} else {
pod := k.InitPod(name, service, opt)
pod := k.InitPod(name, service)
objects = append(objects, pod)
}
envConfigMaps := k.PargeEnvFiletoConfigMaps(name, service, opt)
@ -1905,7 +1707,7 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
service.ServiceType = "Headless"
}
k.configKubeServiceAndIngressForService(service, name, &objects)
err := k.UpdateKubernetesObjects(name, service, opt, &objects, &komposeObject)
err := k.UpdateKubernetesObjects(name, service, opt, &objects)
if err != nil {
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
}
@ -1961,12 +1763,6 @@ func (k *Kubernetes) UpdateController(obj runtime.Object, updateTemplate func(*a
return errors.Wrap(err, "updateTemplate failed")
}
updateMeta(&t.ObjectMeta)
case *batchv1.Job:
err = updateTemplate(&t.Spec.Template)
if err != nil {
return errors.Wrap(err, "updateTemplate failed")
}
updateMeta(&t.ObjectMeta)
case *deployapi.DeploymentConfig:
err = updateTemplate(t.Spec.Template)
if err != nil {

View File

@ -611,9 +611,7 @@ func TestRestartOnFailure(t *testing.T) {
func TestInitPodSpec(t *testing.T) {
name := "foo"
k := Kubernetes{}
service := newServiceConfig()
opt := kobject.ConvertOptions{}
result := k.InitPodSpec(name, service, opt)
result := k.InitPodSpec(name, newServiceConfig().Image, "")
if result.Containers[0].Name != "foo" && result.Containers[0].Image != "image" {
t.Fatalf("Pod object not found")
}
@ -955,7 +953,7 @@ func TestHealthCheckOnMultipleContainers(t *testing.T) {
func TestCreatePVC(t *testing.T) {
storageClassName := "custom-storage-class-name"
k := Kubernetes{}
result, err := k.CreatePVC("", "", PVCRequestSize, "", storageClassName, nil, kobject.ConvertOptions{})
result, err := k.CreatePVC("", "", PVCRequestSize, "", storageClassName)
if err != nil {
t.Error(errors.Wrap(err, "k.CreatePVC failed"))
}

View File

@ -151,7 +151,7 @@ func initBuildConfig(name string, service kobject.ServiceConfig, repo string, br
}
// initDeploymentConfig initializes OpenShifts DeploymentConfig object
func (o *OpenShift) initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int, opt kobject.ConvertOptions) *deployapi.DeploymentConfig {
func (o *OpenShift) initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int) *deployapi.DeploymentConfig {
containerName := []string{name}
// Properly add tags to the image name
@ -164,14 +164,9 @@ func (o *OpenShift) initDeploymentConfig(name string, service kobject.ServiceCon
var podSpec corev1.PodSpec
if len(service.Configs) > 0 {
podSpec = o.InitPodSpecWithConfigMap(name, service, opt)
podSpec = o.InitPodSpecWithConfigMap(name, " ", service)
} else {
podSpec = o.InitPodSpec(name, service, opt)
}
// OpenShift: Set image to a space - actual image comes from ImageStream trigger
for i := range podSpec.Containers {
podSpec.Containers[i].Image = " "
podSpec = o.InitPodSpec(name, " ", "")
}
dc := &deployapi.DeploymentConfig{
@ -338,10 +333,10 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C
}
if service.CronJobSchedule != "" {
cronJob := o.InitCJ(name, service, service.CronJobSchedule, service.CronJobConcurrencyPolicy, service.CronJobBackoffLimit, opt)
cronJob := o.InitCJ(name, service, service.CronJobSchedule, service.CronJobConcurrencyPolicy, service.CronJobBackoffLimit)
objects = append(objects, cronJob)
} else {
pod := o.InitPod(name, service, opt)
pod := o.InitPod(name, service)
objects = append(objects, pod)
}
@ -351,7 +346,7 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C
objects = o.CreateWorkloadAndConfigMapObjects(name, service, opt)
if opt.CreateDeploymentConfig {
objects = append(objects, o.initDeploymentConfig(name, service, replica, opt)) // OpenShift DeploymentConfigs
objects = append(objects, o.initDeploymentConfig(name, service, replica)) // OpenShift DeploymentConfigs
// create ImageStream after deployment (creating IS will trigger new deployment)
objects = append(objects, o.initImageStream(name, service, opt))
}
@ -431,7 +426,7 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C
}
}
err := o.UpdateKubernetesObjects(name, service, opt, &objects, &komposeObject)
err := o.UpdateKubernetesObjects(name, service, opt, &objects)
if err != nil {
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
}

View File

@ -74,12 +74,9 @@ func TestOpenShiftUpdateKubernetesObjects(t *testing.T) {
o := OpenShift{}
serviceConfig := newServiceConfig()
opt := kobject.ConvertOptions{}
komposeObject := kobject.KomposeObject{
ServiceConfigs: map[string]kobject.ServiceConfig{"foobar": serviceConfig},
}
object = append(object, o.initDeploymentConfig("foobar", serviceConfig, 3, opt))
o.UpdateKubernetesObjects("foobar", serviceConfig, opt, &object, &komposeObject)
object = append(object, o.initDeploymentConfig("foobar", serviceConfig, 3))
o.UpdateKubernetesObjects("foobar", serviceConfig, opt, &object)
for _, obj := range object {
switch tobj := obj.(type) {
@ -98,8 +95,7 @@ func TestOpenShiftUpdateKubernetesObjects(t *testing.T) {
func TestInitDeploymentConfig(t *testing.T) {
o := OpenShift{}
opt := kobject.ConvertOptions{}
spec := o.initDeploymentConfig("foobar", newServiceConfig(), 1, opt)
spec := o.initDeploymentConfig("foobar", newServiceConfig(), 1)
// Check that "foobar" is used correctly as a name
if spec.Spec.Template.Spec.Containers[0].Name != "foobar" {

View File

@ -2,7 +2,7 @@ package version
var (
// VERSION is version number that will be displayed when running ./kompose version
VERSION = "1.37.0-zenith-0.0.1"
VERSION = "1.37.0"
// GITCOMMIT is hash of the commit that will be displayed when running ./kompose version
// this will be overwritten when running build like this: go build -ldflags="-X github.com/kubernetes/kompose/pkg/version.GITCOMMIT=$(GITCOMMIT)"
// HEAD is default indicating that this was not set during build