11 changed files with 386 additions and 54 deletions
+49
View File
@@ -0,0 +1,49 @@
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"
+5 -5
View File
@@ -49,11 +49,11 @@ install:
.PHONY: cross .PHONY: cross
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=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=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=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=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=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=darwin GOARCH=arm64 CGO_ENABLED=0 GO111MODULE=on go build ${BUILD_FLAGS} -installsuffix cgo -o "bin/kompose-darwin-arm64" main.go
.PHONY: clean .PHONY: clean
clean: clean:
+1 -1
View File
@@ -1 +1 @@
1.37.0 1.37.0-zenith-0.0.1
+6 -1
View File
@@ -261,7 +261,12 @@ func Convert(opt kobject.ConvertOptions) ([]runtime.Object, error) {
} }
// Print output // Print output
err = kubernetes.PrintList(objects, opt, komposeObject) // 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)
if err != nil { if err != nil {
log.Fatalf(err.Error()) log.Fatalf(err.Error())
} }
+13 -12
View File
@@ -223,18 +223,19 @@ func (port *Ports) ID() string {
// Volumes holds the volume struct of container // Volumes holds the volume struct of container
type Volumes struct { type Volumes struct {
SvcName string // Service name to which volume is linked SvcName string // Service name to which volume is linked
MountPath string // Mountpath extracted from docker-compose file MountPath string // Mountpath extracted from docker-compose file
VFrom string // denotes service name from which volume is coming VFrom string // denotes service name from which volume is coming
VolumeName string // name of volume if provided explicitly VolumeName string // name of volume if provided explicitly
Host string // host machine address Host string // host machine address
Container string // Mountpath Container string // Mountpath
Mode string // access mode for volume Mode string // access mode for volume
PVCName string // name of PVC PVCName string // name of PVC
PVCSize string // PVC size PVCSize string // PVC size
SelectorValue string // Value of the label selector SelectorValue string // Value of the label selector
VolumeType string // Type of volume (e.g., "secret") VolumeType string // Type of volume (e.g., "secret")
SecretName string // Name of Kubernetes Secret (if VolumeType is "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")
} }
// Placement holds the placement struct of container // Placement holds the placement struct of container
+18 -5
View File
@@ -606,7 +606,13 @@ func dockerComposeToKomposeMapping(composeObject *types.Project) (kobject.Kompos
komposeObject.ServiceConfigs[normalizeServiceNames(name)] = serviceConfig komposeObject.ServiceConfigs[normalizeServiceNames(name)] = serviceConfig
} }
handleVolume(&komposeObject, &composeObject.Volumes) // 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)
return komposeObject, nil return komposeObject, nil
} }
@@ -839,14 +845,15 @@ func handleVolume(komposeObject *kobject.KomposeObject, volumes *types.Volumes)
errors.Wrap(err, "could not retrieve vvolume") errors.Wrap(err, "could not retrieve vvolume")
} }
for volName, vol := range vols { for volName, vol := range vols {
size, selector, volumeType, secretName := getVolumeLabels(vol.VolumeName, volumes) size, selector, volumeType, secretName, annotations := getVolumeLabels(vol.VolumeName, volumes)
if len(size) > 0 || len(selector) > 0 || len(volumeType) > 0 || len(secretName) > 0 { if len(size) > 0 || len(selector) > 0 || len(volumeType) > 0 || len(secretName) > 0 || len(annotations) > 0 {
// We can't assign value to struct field in map while iterating over it, so temporary variable `temp` is used here // 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] var temp = vols[volName]
temp.PVCSize = size temp.PVCSize = size
temp.SelectorValue = selector temp.SelectorValue = selector
temp.VolumeType = volumeType temp.VolumeType = volumeType
temp.SecretName = secretName temp.SecretName = secretName
temp.Annotations = annotations
vols[volName] = temp vols[volName] = temp
} }
} }
@@ -951,8 +958,9 @@ func getVol(toFind kobject.Volumes, Vols []kobject.Volumes) (bool, kobject.Volum
return false, kobject.Volumes{} return false, kobject.Volumes{}
} }
func getVolumeLabels(name string, volumes *types.Volumes) (string, string, string, string) { func getVolumeLabels(name string, volumes *types.Volumes) (string, string, string, string, map[string]string) {
size, selector, volumeType, secretName := "", "", "", "" size, selector, volumeType, secretName := "", "", "", ""
annotations := make(map[string]string)
if volume, ok := (*volumes)[name]; ok { if volume, ok := (*volumes)[name]; ok {
log.Debugf("Getting labels for volume %s, labels: %v", name, volume.Labels) log.Debugf("Getting labels for volume %s, labels: %v", name, volume.Labels)
@@ -965,6 +973,11 @@ func getVolumeLabels(name string, volumes *types.Volumes) (string, string, strin
volumeType = value volumeType = value
} else if key == "kompose.volume.secret-name" { } else if key == "kompose.volume.secret-name" {
secretName = value 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 != "" { if volumeType != "" {
@@ -974,7 +987,7 @@ func getVolumeLabels(name string, volumes *types.Volumes) (string, string, strin
log.Debugf("Volume %s not found in volumes map", name) log.Debugf("Volume %s not found in volumes map", name)
} }
return size, selector, volumeType, secretName return size, selector, volumeType, secretName, annotations
} }
// getGroupAdd will return group in int64 format // getGroupAdd will return group in int64 format
+2
View File
@@ -100,6 +100,8 @@ const (
LabelVolumeType = "kompose.volume.type" LabelVolumeType = "kompose.volume.type"
// LabelVolumeSecretName defines the name of the Kubernetes Secret to use // LabelVolumeSecretName defines the name of the Kubernetes Secret to use
LabelVolumeSecretName = "kompose.volume.secret-name" 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 defines min pod replicas
LabelHpaMinReplicas = "kompose.hpa.replicas.min" LabelHpaMinReplicas = "kompose.hpa.replicas.min"
// LabelHpaMaxReplicas defines max pod replicas // LabelHpaMaxReplicas defines max pod replicas
+198 -22
View File
@@ -89,7 +89,12 @@ type ServiceValues struct {
Tag string Tag string
PullPolicy string PullPolicy string
} }
Env map[string]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 // splitImage splits "repo:tag" or "repo" into repository and tag
@@ -129,9 +134,84 @@ func unquoteHelmTemplates(yamlBytes []byte) []byte {
return []byte(strings.Join(lines, "\n")) return []byte(strings.Join(lines, "\n"))
} }
// extractValuesFromKomposeObject extracts values from KomposeObject for values.yaml // templatePVCStorage replaces PVC storage values with Helm templates
func extractValuesFromKomposeObject(komposeObject kobject.KomposeObject) map[string]ServiceValues { // This post-processes YAML because resource.Quantity validates input and rejects template strings
values := make(map[string]ServiceValues) 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 { for serviceName, service := range komposeObject.ServiceConfigs {
svcValues := ServiceValues{} svcValues := ServiceValues{}
@@ -154,16 +234,23 @@ func extractValuesFromKomposeObject(komposeObject kobject.KomposeObject) map[str
svcValues.Env[envVar.Name] = envVar.Value svcValues.Env[envVar.Name] = envVar.Value
} }
values[serviceName] = svcValues // Extract replicas (default to 1 if not set)
if service.Replicas > 0 {
svcValues.Replicas = service.Replicas
} else {
svcValues.Replicas = 1
}
serviceValues[serviceName] = svcValues
} }
return values return serviceValues, nil
} }
/** /**
* Generate Helm Chart configuration * Generate Helm Chart configuration
*/ */
func generateHelm(dirName string, values map[string]ServiceValues) error { func generateHelm(dirName string, serviceValues map[string]ServiceValues, persistenceValues map[string]PersistenceValues) error {
type ChartDetails struct { type ChartDetails struct {
Name string Name string
} }
@@ -220,8 +307,8 @@ home:
} }
/* Create the values.yaml file */ /* Create the values.yaml file */
if len(values) > 0 { if len(serviceValues) > 0 || len(persistenceValues) > 0 {
valuesYAML, err := generateValuesYAML(values) valuesYAML, err := generateValuesYAML(serviceValues, persistenceValues)
if err != nil { if err != nil {
return errors.Wrap(err, "Failed to generate values.yaml") return errors.Wrap(err, "Failed to generate values.yaml")
} }
@@ -236,19 +323,19 @@ home:
} }
// generateValuesYAML creates values.yaml content from extracted values // generateValuesYAML creates values.yaml content from extracted values
func generateValuesYAML(values map[string]ServiceValues) ([]byte, error) { func generateValuesYAML(serviceValues map[string]ServiceValues, persistenceValues map[string]PersistenceValues) ([]byte, error) {
// Build hierarchical structure: serviceName -> image -> {repository, tag, pullPolicy} // Build hierarchical structure: serviceName -> image -> {repository, tag, pullPolicy}
valuesMap := make(map[string]interface{}) valuesMap := make(map[string]interface{})
// Sort service names for consistent output // Sort service names for consistent output
serviceNames := make([]string, 0, len(values)) serviceNames := make([]string, 0, len(serviceValues))
for name := range values { for name := range serviceValues {
serviceNames = append(serviceNames, name) serviceNames = append(serviceNames, name)
} }
sort.Strings(serviceNames) sort.Strings(serviceNames)
for _, serviceName := range serviceNames { for _, serviceName := range serviceNames {
svcValues := values[serviceName] svcValues := serviceValues[serviceName]
serviceMap := map[string]interface{}{ serviceMap := map[string]interface{}{
"image": map[string]string{ "image": map[string]string{
"repository": svcValues.Image.Repository, "repository": svcValues.Image.Repository,
@@ -262,9 +349,32 @@ func generateValuesYAML(values map[string]ServiceValues) ([]byte, error) {
serviceMap["env"] = svcValues.Env serviceMap["env"] = svcValues.Env
} }
// Add replicas
serviceMap["replicas"] = svcValues.Replicas
valuesMap[serviceName] = serviceMap 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 // Use marshalWithIndent for consistent 2-space indentation
yamlBytes, err := marshalWithIndent(valuesMap, 2) yamlBytes, err := marshalWithIndent(valuesMap, 2)
if err != nil { if err != nil {
@@ -312,7 +422,7 @@ func getDirName(opt kobject.ConvertOptions) string {
} }
// PrintList will take the data converted and decide on the commandline attributes given // PrintList will take the data converted and decide on the commandline attributes given
func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObject kobject.KomposeObject) error { func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObject kobject.KomposeObject, k8sTransformer *Kubernetes) error {
var f *os.File var f *os.File
dirName := getDirName(opt) dirName := getDirName(opt)
log.Debugf("Target Dir: %s", dirName) log.Debugf("Target Dir: %s", dirName)
@@ -344,11 +454,19 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObje
} }
var files []string var files []string
var imageValues map[string]ServiceValues var serviceValues map[string]ServiceValues
var persistenceValues map[string]PersistenceValues
// Extract values from KomposeObject for values.yaml // Extract values from KomposeObject for values.yaml
if opt.CreateChart { if opt.CreateChart {
imageValues = extractValuesFromKomposeObject(komposeObject) 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 // if asked to print to stdout or to put in single file
@@ -372,6 +490,7 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObje
// Unquote Helm templates if generating chart // Unquote Helm templates if generating chart
if opt.CreateChart { if opt.CreateChart {
data = unquoteHelmTemplates(data) data = unquoteHelmTemplates(data)
data = templatePVCStorage(data, persistenceValues)
} }
// this part add --- which unifies the file // this part add --- which unifies the file
@@ -425,6 +544,8 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObje
// Unquote Helm templates if generating chart // Unquote Helm templates if generating chart
if opt.CreateChart { if opt.CreateChart {
data = unquoteHelmTemplates(data) data = unquoteHelmTemplates(data)
data = templatePVCStorage(data, persistenceValues)
data = templateReplicas(data, objectMeta.Name)
} }
file, err = transformer.Print(objectMeta.Name, finalDirName, strings.ToLower(typeMeta.Kind), data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider) file, err = transformer.Print(objectMeta.Name, finalDirName, strings.ToLower(typeMeta.Kind), data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider)
@@ -436,7 +557,7 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObje
} }
} }
if opt.CreateChart { if opt.CreateChart {
err = generateHelm(dirName, imageValues) err = generateHelm(dirName, serviceValues, persistenceValues)
if err != nil { if err != nil {
return errors.Wrap(err, "generateHelm failed") return errors.Wrap(err, "generateHelm failed")
} }
@@ -499,6 +620,24 @@ func removeEmptyInterfaces(obj interface{}) interface{} {
} }
// Convert JSON to YAML. // 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) { func jsonToYaml(j []byte, spaces int) ([]byte, error) {
// Convert the JSON to an object. // Convert the JSON to an object.
var jsonObj interface{} var jsonObj interface{}
@@ -512,10 +651,20 @@ func jsonToYaml(j []byte, spaces int) ([]byte, error) {
return nil, err return nil, err
} }
jsonObj = removeEmptyInterfaces(jsonObj) 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 var b bytes.Buffer
encoder := yaml.NewEncoder(&b) encoder := yaml.NewEncoder(&b)
encoder.SetIndent(spaces) encoder.SetIndent(spaces)
if err := encoder.Encode(jsonObj); err != nil { if err := encoder.Encode(&node); err != nil {
return nil, err return nil, err
} }
return b.Bytes(), nil return b.Bytes(), nil
@@ -697,7 +846,7 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
} }
// Configure the container volumes. // Configure the container volumes.
volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(name, service) volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(name, service, opt)
if err != nil { if err != nil {
return errors.Wrap(err, "k.ConfigVolumes failed") return errors.Wrap(err, "k.ConfigVolumes failed")
} }
@@ -708,7 +857,15 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
volumesMount = append(volumesMount, TmpVolumesMount...) volumesMount = append(volumesMount, TmpVolumesMount...)
} }
if pvc != nil && opt.Controller != StatefulStateController { // 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 {
// Looping on the slice pvc instead of `*objects = append(*objects, pvc...)` // Looping on the slice pvc instead of `*objects = append(*objects, pvc...)`
// because the type of objects and pvc is different, but when doing append // 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 // one element at a time it gets converted to runtime.Object for objects slice
@@ -755,7 +912,7 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
} }
// Configure init container volumes // Configure init container volumes
initVolumeMounts, initVolumes, initPvc, initCms, err := k.ConfigVolumes(initSvcName, initService) initVolumeMounts, initVolumes, initPvc, initCms, err := k.ConfigVolumes(initSvcName, initService, opt)
if err != nil { if err != nil {
return errors.Wrapf(err, "k.ConfigVolumes failed for init container %s", initSvcName) return errors.Wrapf(err, "k.ConfigVolumes failed for init container %s", initSvcName)
} }
@@ -784,7 +941,8 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
} }
} }
if initPvc != nil { // Skip PVCs for init containers if parent service is StatefulSet
if initPvc != nil && !isStatefulSet {
for _, p := range initPvc { for _, p := range initPvc {
if !existingObjectNames[p.Name] { if !existingObjectNames[p.Name] {
*objects = append(*objects, p) *objects = append(*objects, p)
@@ -995,8 +1153,26 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
persistentVolumeClaims[i] = *persistentVolumeClaim persistentVolumeClaims[i] = *persistentVolumeClaim
persistentVolumeClaims[i].APIVersion = "" persistentVolumeClaims[i].APIVersion = ""
persistentVolumeClaims[i].Kind = "" 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 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
} }
} }
} }
+92 -6
View File
@@ -55,6 +55,8 @@ import (
type Kubernetes struct { type Kubernetes struct {
// the user provided options from the command line // the user provided options from the command line
Opt kobject.ConvertOptions 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 // helmValuesPath generates a Helm template path using index notation
@@ -81,6 +83,8 @@ const (
DaemonSetController = "daemonset" DaemonSetController = "daemonset"
// StatefulStateController is controller type for StatefulSet // StatefulStateController is controller type for StatefulSet
StatefulStateController = "statefulset" StatefulStateController = "statefulset"
// JobController is controller type for Job
JobController = "job"
) )
// CheckUnsupportedKey checks if given komposeObject contains // CheckUnsupportedKey checks if given komposeObject contains
@@ -523,6 +527,52 @@ func (k *Kubernetes) InitDS(name string, service kobject.ServiceConfig, opt kobj
return ds 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 // 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, opt kobject.ConvertOptions) *appsv1.StatefulSet {
var podSpec api.PodSpec var podSpec api.PodSpec
@@ -688,8 +738,12 @@ func (k *Kubernetes) CreateSecrets(komposeObject kobject.KomposeObject) ([]*api.
} }
// CreatePVC initializes PersistentVolumeClaim // CreatePVC initializes PersistentVolumeClaim
func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorValue string, storageClassName string) (*api.PersistentVolumeClaim, error) { func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorValue string, storageClassName string, annotations map[string]string, opt kobject.ConvertOptions) (*api.PersistentVolumeClaim, error) {
volSize, err := resource.ParseQuantity(size) var volSize resource.Quantity
var err error
// Parse the size value
volSize, err = resource.ParseQuantity(size)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "resource.ParseQuantity failed, Error parsing size") return nil, errors.Wrap(err, "resource.ParseQuantity failed, Error parsing size")
} }
@@ -712,6 +766,16 @@ 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 { if len(selectorValue) > 0 {
pvc.Spec.Selector = &metav1.LabelSelector{ pvc.Spec.Selector = &metav1.LabelSelector{
MatchLabels: transformer.ConfigLabels(selectorValue), MatchLabels: transformer.ConfigLabels(selectorValue),
@@ -1000,7 +1064,11 @@ func (k *Kubernetes) getSecretPathsLegacy(secretConfig types.ServiceSecretConfig
} }
// 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, 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)
}
volumeMounts := []api.VolumeMount{} volumeMounts := []api.VolumeMount{}
volumes := []api.Volume{} volumes := []api.Volume{}
var PVCs []*api.PersistentVolumeClaim var PVCs []*api.PersistentVolumeClaim
@@ -1170,12 +1238,17 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) (
} }
} }
createdPVC, err := k.CreatePVC(volumeName, volume.Mode, defaultSize, volume.SelectorValue, storageClassName) createdPVC, err := k.CreatePVC(volumeName, volume.Mode, defaultSize, volume.SelectorValue, storageClassName, volume.Annotations, opt)
if err != nil { if err != nil {
return nil, nil, nil, nil, errors.Wrap(err, "k.CreatePVC failed") 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) PVCs = append(PVCs, createdPVC)
} }
} }
@@ -1454,6 +1527,10 @@ func (k *Kubernetes) CreateWorkloadAndConfigMapObjects(name string, service kobj
objects = append(objects, k.InitSS(name, service, replica, opt)) objects = append(objects, k.InitSS(name, service, replica, opt))
} }
if opt.Controller == JobController {
objects = append(objects, k.InitJob(name, service, opt))
}
envConfigMaps := k.PargeEnvFiletoConfigMaps(name, service, opt) envConfigMaps := k.PargeEnvFiletoConfigMaps(name, service, opt)
objects = append(objects, envConfigMaps...) objects = append(objects, envConfigMaps...)
return objects return objects
@@ -1709,7 +1786,7 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
k.configKubeServiceAndIngressForService(service, groupName, &objects) k.configKubeServiceAndIngressForService(service, groupName, &objects)
// Configure the container volumes. // Configure the container volumes.
volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(groupName, service) volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(groupName, service, opt)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "k.ConfigVolumes failed") return nil, errors.Wrap(err, "k.ConfigVolumes failed")
} }
@@ -1807,7 +1884,10 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
} }
// Generate pod or cronjob and configmap objects // Generate pod or cronjob and configmap objects
if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() { // 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.CronJobSchedule != "" { if service.CronJobSchedule != "" {
log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart) 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, opt)
@@ -1881,6 +1961,12 @@ func (k *Kubernetes) UpdateController(obj runtime.Object, updateTemplate func(*a
return errors.Wrap(err, "updateTemplate failed") return errors.Wrap(err, "updateTemplate failed")
} }
updateMeta(&t.ObjectMeta) 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: case *deployapi.DeploymentConfig:
err = updateTemplate(t.Spec.Template) err = updateTemplate(t.Spec.Template)
if err != nil { if err != nil {
@@ -955,7 +955,7 @@ func TestHealthCheckOnMultipleContainers(t *testing.T) {
func TestCreatePVC(t *testing.T) { func TestCreatePVC(t *testing.T) {
storageClassName := "custom-storage-class-name" storageClassName := "custom-storage-class-name"
k := Kubernetes{} k := Kubernetes{}
result, err := k.CreatePVC("", "", PVCRequestSize, "", storageClassName) result, err := k.CreatePVC("", "", PVCRequestSize, "", storageClassName, nil, kobject.ConvertOptions{})
if err != nil { if err != nil {
t.Error(errors.Wrap(err, "k.CreatePVC failed")) t.Error(errors.Wrap(err, "k.CreatePVC failed"))
} }
+1 -1
View File
@@ -2,7 +2,7 @@ package version
var ( var (
// VERSION is version number that will be displayed when running ./kompose version // VERSION is version number that will be displayed when running ./kompose version
VERSION = "1.37.0" VERSION = "1.37.0-zenith-0.0.1"
// GITCOMMIT is hash of the commit that will be displayed when running ./kompose version // 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)" // 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 // HEAD is default indicating that this was not set during build