forked from LaconicNetwork/kompose
Generate template placeholders charts and support init containers and jobs (#1)
Part of https://plan.wireit.in/deepstack/browse/VUL-265/ Reviewed-on: LaconicNetwork/kompose#1 Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com> Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
This commit is contained in:
parent
fb0539e64c
commit
e5e0d5e117
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
|||||||
run: make bin
|
run: make bin
|
||||||
|
|
||||||
- name: Upload a Build Artifact
|
- name: Upload a Build Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: "kompose"
|
name: "kompose"
|
||||||
path: "kompose"
|
path: "kompose"
|
||||||
|
|||||||
49
.github/workflows/publish.yml
vendored
Normal file
49
.github/workflows/publish.yml
vendored
Normal 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"
|
||||||
40
.github/workflows/test.yml
vendored
40
.github/workflows/test.yml
vendored
@ -27,23 +27,23 @@ jobs:
|
|||||||
- name: Perform cross compile
|
- name: Perform cross compile
|
||||||
if: ${{ matrix.cross_compile }}
|
if: ${{ matrix.cross_compile }}
|
||||||
run: make cross
|
run: make cross
|
||||||
docs:
|
# docs:
|
||||||
name: Build docs and Coveralls integration
|
# name: Build docs and Coveralls integration
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
needs: test
|
# needs: test
|
||||||
steps:
|
# steps:
|
||||||
- uses: actions/checkout@v4
|
# - uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v5
|
# - uses: actions/setup-go@v5
|
||||||
with:
|
# with:
|
||||||
go-version: ^1.21
|
# go-version: ^1.21
|
||||||
- name: Install dyff
|
# - name: Install dyff
|
||||||
run: go install github.com/homeport/dyff/cmd/dyff@v1.5.8
|
# run: go install github.com/homeport/dyff/cmd/dyff@v1.5.8
|
||||||
- name: Create .coverprofile for each targeted directory by re:running tests
|
# - name: Create .coverprofile for each targeted directory by re:running tests
|
||||||
run: make test
|
# run: make test
|
||||||
- name: Collect all .coverprofile files and save it to one file gover.coverprofile
|
# - name: Collect all .coverprofile files and save it to one file gover.coverprofile
|
||||||
run: gover
|
# run: gover
|
||||||
- name: Send coverage
|
# - name: Send coverage
|
||||||
run: goveralls -coverprofile=gover.coverprofile -service=github
|
# run: goveralls -coverprofile=gover.coverprofile -service=github
|
||||||
env:
|
# env:
|
||||||
# As per https://github.com/mattn/goveralls#github-actions
|
# # As per https://github.com/mattn/goveralls#github-actions
|
||||||
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
# COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
10
Makefile
10
Makefile
@ -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 @@
|
|||||||
1.37.0
|
1.37.0-zenith-0.0.1
|
||||||
|
|||||||
@ -261,7 +261,12 @@ func Convert(opt kobject.ConvertOptions) ([]runtime.Object, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print output
|
// Print output
|
||||||
err = kubernetes.PrintList(objects, opt)
|
// 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())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -233,6 +233,9 @@ type Volumes struct {
|
|||||||
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")
|
||||||
|
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
|
||||||
|
|||||||
@ -839,12 +839,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 := getVolumeLabels(vol.VolumeName, volumes)
|
size, selector, volumeType, secretName, annotations := getVolumeLabels(vol.VolumeName, volumes)
|
||||||
if len(size) > 0 || len(selector) > 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.SecretName = secretName
|
||||||
|
temp.Annotations = annotations
|
||||||
vols[volName] = temp
|
vols[volName] = temp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -949,20 +952,36 @@ 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) {
|
func getVolumeLabels(name string, volumes *types.Volumes) (string, string, string, string, map[string]string) {
|
||||||
size, selector := "", ""
|
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)
|
||||||
for key, value := range volume.Labels {
|
for key, value := range volume.Labels {
|
||||||
if key == "kompose.volume.size" {
|
if key == "kompose.volume.size" {
|
||||||
size = value
|
size = value
|
||||||
} else if key == "kompose.volume.selector" {
|
} else if key == "kompose.volume.selector" {
|
||||||
selector = value
|
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
|
return size, selector, volumeType, secretName, annotations
|
||||||
}
|
}
|
||||||
|
|
||||||
// getGroupAdd will return group in int64 format
|
// getGroupAdd will return group in int64 format
|
||||||
|
|||||||
@ -94,6 +94,14 @@ const (
|
|||||||
LabelInitContainerImage = "kompose.init.containers.image"
|
LabelInitContainerImage = "kompose.init.containers.image"
|
||||||
// LabelInitContainerCommand defines commands
|
// LabelInitContainerCommand defines commands
|
||||||
LabelInitContainerCommand = "kompose.init.containers.command"
|
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 defines min pod replicas
|
||||||
LabelHpaMinReplicas = "kompose.hpa.replicas.min"
|
LabelHpaMinReplicas = "kompose.hpa.replicas.min"
|
||||||
// LabelHpaMaxReplicas defines max pod replicas
|
// LabelHpaMaxReplicas defines max pod replicas
|
||||||
|
|||||||
@ -82,10 +82,150 @@ type DeploymentMapping struct {
|
|||||||
TargetDeploymentName string
|
TargetDeploymentName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServiceValues holds the extracted values for templating
|
||||||
|
type ServiceValues struct {
|
||||||
|
Image struct {
|
||||||
|
Repository string
|
||||||
|
Tag string
|
||||||
|
PullPolicy string
|
||||||
|
}
|
||||||
|
Env map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// TODO: 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)
|
||||||
|
|
||||||
|
// Check if this is a PersistentVolumeClaim
|
||||||
|
if !strings.Contains(yamlStr, "kind: PersistentVolumeClaim") {
|
||||||
|
return yamlBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract PVC name from metadata
|
||||||
|
var pvcName string
|
||||||
|
lines := strings.Split(yamlStr, "\n")
|
||||||
|
inMetadata := false
|
||||||
|
for _, line := range lines {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
if trimmed == "metadata:" {
|
||||||
|
inMetadata = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if inMetadata && strings.HasPrefix(trimmed, "name:") {
|
||||||
|
parts := strings.SplitN(trimmed, ":", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
pvcName = strings.TrimSpace(parts[1])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Exit metadata section if we hit another top-level key
|
||||||
|
if inMetadata && len(line) > 0 && line[0] != ' ' && line[0] != '\t' && !strings.HasPrefix(trimmed, "name:") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found the PVC name and it's in our persistence values, template it
|
||||||
|
if pvcName != "" {
|
||||||
|
if _, exists := persistenceValues[pvcName]; exists {
|
||||||
|
templateStr := "{{ index .Values \"persistence\" \"" + pvcName + "\" \"size\" }}"
|
||||||
|
|
||||||
|
// Replace storage value with template
|
||||||
|
for i, line := range lines {
|
||||||
|
if strings.Contains(line, "storage:") {
|
||||||
|
log.Debugf("Found storage line: %q", line)
|
||||||
|
prefix := line[:strings.Index(line, ":")+2]
|
||||||
|
log.Debugf("Prefix: %q, Template: %q", prefix, templateStr)
|
||||||
|
lines[i] = prefix + templateStr
|
||||||
|
log.Debugf("New line: %q", lines[i])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yamlStr = strings.Join(lines, "\n")
|
||||||
|
log.Debugf("Final templated YAML:\n%s", yamlStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(yamlStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceValues[serviceName] = svcValues
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceValues, nil
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate Helm Chart configuration
|
* Generate Helm Chart configuration
|
||||||
*/
|
*/
|
||||||
func generateHelm(dirName string) error {
|
func generateHelm(dirName string, serviceValues map[string]ServiceValues, persistenceValues map[string]PersistenceValues) error {
|
||||||
type ChartDetails struct {
|
type ChartDetails struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
@ -141,10 +281,81 @@ home:
|
|||||||
return err
|
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))
|
log.Infof("chart created in %q\n", dirName+string(os.PathSeparator))
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// Check if given path is a directory
|
||||||
func isDir(name string) (bool, error) {
|
func isDir(name string) (bool, error) {
|
||||||
// Open file to get stat later
|
// Open file to get stat later
|
||||||
@ -183,7 +394,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) 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)
|
||||||
@ -215,6 +426,21 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var files []string
|
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
|
// if asked to print to stdout or to put in single file
|
||||||
// we will create a list
|
// we will create a list
|
||||||
if opt.ToStdout || f != nil {
|
if opt.ToStdout || f != nil {
|
||||||
@ -232,6 +458,13 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error in marshalling the List: %v", err)
|
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
|
// this part add --- which unifies the file
|
||||||
data = []byte(fmt.Sprintf("---\n%s", data))
|
data = []byte(fmt.Sprintf("---\n%s", data))
|
||||||
printVal, err := transformer.Print("", dirName, "", data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider)
|
printVal, err := transformer.Print("", dirName, "", data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider)
|
||||||
@ -265,6 +498,7 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
|
|||||||
var typeMeta metav1.TypeMeta
|
var typeMeta metav1.TypeMeta
|
||||||
var objectMeta metav1.ObjectMeta
|
var objectMeta metav1.ObjectMeta
|
||||||
|
|
||||||
|
// Get object metadata first for templating
|
||||||
if us, ok := v.(*unstructured.Unstructured); ok {
|
if us, ok := v.(*unstructured.Unstructured); ok {
|
||||||
typeMeta = metav1.TypeMeta{
|
typeMeta = metav1.TypeMeta{
|
||||||
Kind: us.GetKind(),
|
Kind: us.GetKind(),
|
||||||
@ -275,15 +509,16 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val := reflect.ValueOf(v).Elem()
|
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)
|
typeMeta = val.FieldByName("TypeMeta").Interface().(metav1.TypeMeta)
|
||||||
|
|
||||||
// Use reflect to access ObjectMeta struct inside runtime.Object.
|
|
||||||
// cast it to correct type - api.ObjectMeta
|
|
||||||
objectMeta = val.FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta)
|
objectMeta = val.FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unquote Helm templates if generating chart
|
||||||
|
if opt.CreateChart {
|
||||||
|
data = unquoteHelmTemplates(data)
|
||||||
|
data = templatePVCStorage(data, persistenceValues)
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "transformer.Print failed")
|
return errors.Wrap(err, "transformer.Print failed")
|
||||||
@ -293,7 +528,7 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if opt.CreateChart {
|
if opt.CreateChart {
|
||||||
err = generateHelm(dirName)
|
err = generateHelm(dirName, serviceValues, persistenceValues)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "generateHelm failed")
|
return errors.Wrap(err, "generateHelm failed")
|
||||||
}
|
}
|
||||||
@ -546,7 +781,7 @@ func (k *Kubernetes) UpdateKubernetesObjectsMultipleContainers(name string, serv
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateKubernetesObjects loads configurations to k8s objects
|
// UpdateKubernetesObjects loads configurations to k8s objects
|
||||||
func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object) error {
|
func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object, komposeObject *kobject.KomposeObject) error {
|
||||||
// Configure the environment variables.
|
// Configure the environment variables.
|
||||||
envs, envsFrom, err := ConfigEnvs(service, opt)
|
envs, envsFrom, err := ConfigEnvs(service, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -554,7 +789,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")
|
||||||
}
|
}
|
||||||
@ -586,6 +821,101 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
|
|||||||
// Configure annotations
|
// Configure annotations
|
||||||
annotations := transformer.ConfigAnnotations(service)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if initPvc != nil {
|
||||||
|
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 fills the pod template with the value calculated from config
|
||||||
fillTemplate := func(template *api.PodTemplateSpec) error {
|
fillTemplate := func(template *api.PodTemplateSpec) error {
|
||||||
template.Spec.Containers[0].Name = GetContainerName(service)
|
template.Spec.Containers[0].Name = GetContainerName(service)
|
||||||
@ -722,6 +1052,11 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
|
|||||||
template.Spec.ServiceAccountName = serviceAccountName
|
template.Spec.ServiceAccountName = serviceAccountName
|
||||||
}
|
}
|
||||||
fillInitContainers(template, service)
|
fillInitContainers(template, service)
|
||||||
|
|
||||||
|
// Add init containers from referenced services
|
||||||
|
if len(initContainers) > 0 {
|
||||||
|
template.Spec.InitContainers = append(template.Spec.InitContainers, initContainers...)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -55,13 +55,26 @@ 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
|
||||||
|
// 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
|
// PVCRequestSize (Persistent Volume Claim) has default size
|
||||||
const PVCRequestSize = "100Mi"
|
const PVCRequestSize = "100Mi"
|
||||||
|
|
||||||
// ValidVolumeSet has the different types of valid volumes
|
// ValidVolumeSet has the different types of valid volumes
|
||||||
var ValidVolumeSet = map[string]struct{}{"emptyDir": {}, "hostPath": {}, "configMap": {}, "persistentVolumeClaim": {}}
|
var ValidVolumeSet = map[string]struct{}{"emptyDir": {}, "hostPath": {}, "configMap": {}, "persistentVolumeClaim": {}, "secret": {}}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DeploymentController is controller type for Deployment
|
// DeploymentController is controller type for Deployment
|
||||||
@ -70,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
|
||||||
@ -109,10 +124,17 @@ func (k *Kubernetes) CheckUnsupportedKey(komposeObject *kobject.KomposeObject, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitPodSpec creates the pod specification
|
// InitPodSpec creates the pod specification
|
||||||
func (k *Kubernetes) InitPodSpec(name string, image string, pullSecret string) api.PodSpec {
|
func (k *Kubernetes) InitPodSpec(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) api.PodSpec {
|
||||||
|
image := service.Image
|
||||||
if image == "" {
|
if image == "" {
|
||||||
image = name
|
image = name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inject Helm template for chart generation
|
||||||
|
if opt.CreateChart {
|
||||||
|
image = "{{ " + helmValuesPath(name, "image", "repository") + " }}:{{ " + helmValuesPath(name, "image", "tag") + " }}"
|
||||||
|
}
|
||||||
|
|
||||||
pod := api.PodSpec{
|
pod := api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
@ -121,10 +143,11 @@ func (k *Kubernetes) InitPodSpec(name string, image string, pullSecret string) a
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if pullSecret != "" {
|
|
||||||
|
if service.ImagePullSecret != "" {
|
||||||
pod.ImagePullSecrets = []api.LocalObjectReference{
|
pod.ImagePullSecrets = []api.LocalObjectReference{
|
||||||
{
|
{
|
||||||
Name: pullSecret,
|
Name: service.ImagePullSecret,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,7 +155,7 @@ func (k *Kubernetes) InitPodSpec(name string, image string, pullSecret string) a
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitPodSpecWithConfigMap creates the pod specification
|
// InitPodSpecWithConfigMap creates the pod specification
|
||||||
func (k *Kubernetes) InitPodSpecWithConfigMap(name string, image string, service kobject.ServiceConfig) api.PodSpec {
|
func (k *Kubernetes) InitPodSpecWithConfigMap(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) api.PodSpec {
|
||||||
var volumeMounts []api.VolumeMount
|
var volumeMounts []api.VolumeMount
|
||||||
var volumes []api.Volume
|
var volumes []api.Volume
|
||||||
|
|
||||||
@ -177,6 +200,16 @@ func (k *Kubernetes) InitPodSpecWithConfigMap(name string, image string, service
|
|||||||
volumes = append(volumes, cmVol)
|
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{
|
pod := api.PodSpec{
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
@ -329,6 +362,10 @@ func (k *Kubernetes) IntiConfigMapFromFileOrDir(name, cmName, filePath string, s
|
|||||||
configMap.Annotations = map[string]string{
|
configMap.Annotations = map[string]string{
|
||||||
"use-subpath": "true",
|
"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
|
return configMap, nil
|
||||||
@ -412,12 +449,12 @@ func (k *Kubernetes) InitConfigMapFromFile(name string, service kobject.ServiceC
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitD initializes Kubernetes Deployment object
|
// InitD initializes Kubernetes Deployment object
|
||||||
func (k *Kubernetes) InitD(name string, service kobject.ServiceConfig, replicas int) *appsv1.Deployment {
|
func (k *Kubernetes) InitD(name string, service kobject.ServiceConfig, replicas int, opt kobject.ConvertOptions) *appsv1.Deployment {
|
||||||
var podSpec api.PodSpec
|
var podSpec api.PodSpec
|
||||||
if len(service.Configs) > 0 {
|
if len(service.Configs) > 0 {
|
||||||
podSpec = k.InitPodSpecWithConfigMap(name, service.Image, service)
|
podSpec = k.InitPodSpecWithConfigMap(name, service, opt)
|
||||||
} else {
|
} else {
|
||||||
podSpec = k.InitPodSpec(name, service.Image, service.ImagePullSecret)
|
podSpec = k.InitPodSpec(name, service, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
rp := int32(replicas)
|
rp := int32(replicas)
|
||||||
@ -468,7 +505,7 @@ func (k *Kubernetes) InitD(name string, service kobject.ServiceConfig, replicas
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitDS initializes Kubernetes DaemonSet object
|
// InitDS initializes Kubernetes DaemonSet object
|
||||||
func (k *Kubernetes) InitDS(name string, service kobject.ServiceConfig) *appsv1.DaemonSet {
|
func (k *Kubernetes) InitDS(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) *appsv1.DaemonSet {
|
||||||
ds := &appsv1.DaemonSet{
|
ds := &appsv1.DaemonSet{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: "DaemonSet",
|
Kind: "DaemonSet",
|
||||||
@ -483,20 +520,66 @@ func (k *Kubernetes) InitDS(name string, service kobject.ServiceConfig) *appsv1.
|
|||||||
MatchLabels: transformer.ConfigLabels(name),
|
MatchLabels: transformer.ConfigLabels(name),
|
||||||
},
|
},
|
||||||
Template: api.PodTemplateSpec{
|
Template: api.PodTemplateSpec{
|
||||||
Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret),
|
Spec: k.InitPodSpec(name, service, opt),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return ds
|
return ds
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitSS method initialize a stateful set
|
// InitJob initializes Kubernetes Job object
|
||||||
func (k *Kubernetes) InitSS(name string, service kobject.ServiceConfig, replicas int) *appsv1.StatefulSet {
|
func (k *Kubernetes) InitJob(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) *batchv1.Job {
|
||||||
var podSpec api.PodSpec
|
var podSpec api.PodSpec
|
||||||
if len(service.Configs) > 0 {
|
if len(service.Configs) > 0 {
|
||||||
podSpec = k.InitPodSpecWithConfigMap(name, service.Image, service)
|
podSpec = k.InitPodSpecWithConfigMap(name, service, opt)
|
||||||
} else {
|
} else {
|
||||||
podSpec = k.InitPodSpec(name, service.Image, service.ImagePullSecret)
|
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 {
|
||||||
|
var podSpec api.PodSpec
|
||||||
|
if len(service.Configs) > 0 {
|
||||||
|
podSpec = k.InitPodSpecWithConfigMap(name, service, opt)
|
||||||
|
} else {
|
||||||
|
podSpec = k.InitPodSpec(name, service, opt)
|
||||||
}
|
}
|
||||||
rp := int32(replicas)
|
rp := int32(replicas)
|
||||||
ds := &appsv1.StatefulSet{
|
ds := &appsv1.StatefulSet{
|
||||||
@ -523,7 +606,7 @@ func (k *Kubernetes) InitSS(name string, service kobject.ServiceConfig, replicas
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitCJ initializes Kubernetes CronJob object
|
// InitCJ initializes Kubernetes CronJob object
|
||||||
func (k *Kubernetes) InitCJ(name string, service kobject.ServiceConfig, schedule string, concurrencyPolicy batchv1.ConcurrencyPolicy, backoffLimit *int32) *batchv1.CronJob {
|
func (k *Kubernetes) InitCJ(name string, service kobject.ServiceConfig, schedule string, concurrencyPolicy batchv1.ConcurrencyPolicy, backoffLimit *int32, opt kobject.ConvertOptions) *batchv1.CronJob {
|
||||||
cj := &batchv1.CronJob{
|
cj := &batchv1.CronJob{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: "CronJob",
|
Kind: "CronJob",
|
||||||
@ -540,7 +623,7 @@ func (k *Kubernetes) InitCJ(name string, service kobject.ServiceConfig, schedule
|
|||||||
Spec: batchv1.JobSpec{
|
Spec: batchv1.JobSpec{
|
||||||
BackoffLimit: backoffLimit,
|
BackoffLimit: backoffLimit,
|
||||||
Template: api.PodTemplateSpec{
|
Template: api.PodTemplateSpec{
|
||||||
Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret),
|
Spec: k.InitPodSpec(name, service, opt),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -655,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")
|
||||||
}
|
}
|
||||||
@ -679,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),
|
||||||
@ -967,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
|
||||||
@ -989,13 +1090,15 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Override volume type if specified in service labels.
|
// Override volume type if specified in service labels.
|
||||||
if vt, ok := service.Labels["kompose.volume.type"]; ok {
|
var useSecret bool
|
||||||
|
if vt, ok := service.Labels[compose.LabelVolumeType]; ok {
|
||||||
if _, okk := ValidVolumeSet[vt]; !okk {
|
if _, okk := ValidVolumeSet[vt]; !okk {
|
||||||
return nil, nil, nil, nil, fmt.Errorf("invalid volume type %s specified in label 'kompose.volume.type' in service %s", vt, service.Name)
|
return nil, nil, nil, nil, fmt.Errorf("invalid volume type %s specified in label '%s' in service %s", vt, compose.LabelVolumeType, service.Name)
|
||||||
}
|
}
|
||||||
useEmptyVolumes = vt == "emptyDir"
|
useEmptyVolumes = vt == "emptyDir"
|
||||||
useHostPath = vt == "hostPath"
|
useHostPath = vt == "hostPath"
|
||||||
useConfigMap = vt == "configMap"
|
useConfigMap = vt == "configMap"
|
||||||
|
useSecret = vt == "secret"
|
||||||
}
|
}
|
||||||
|
|
||||||
// config volumes from secret if present
|
// config volumes from secret if present
|
||||||
@ -1009,23 +1112,52 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) (
|
|||||||
for _, volume := range service.Volumes {
|
for _, volume := range service.Volumes {
|
||||||
// check if ro/rw mode is defined, default rw
|
// check if ro/rw mode is defined, default rw
|
||||||
readonly := len(volume.Mode) > 0 && (volume.Mode == "ro" || volume.Mode == "rox")
|
readonly := len(volume.Mode) > 0 && (volume.Mode == "ro" || volume.Mode == "rox")
|
||||||
mountHost := volume.Host
|
|
||||||
if mountHost == "" {
|
// Reset volume type flags for each volume (service-level flags don't apply to individual volumes)
|
||||||
mountHost = volume.MountPath
|
volumeUseSecret := useSecret
|
||||||
}
|
volumeUseConfigMap := useConfigMap
|
||||||
// return useconfigmap and readonly,
|
volumeUseHostPath := useHostPath
|
||||||
// not used asigned readonly because dont break e2e
|
volumeUseEmpty := useEmptyVolumes
|
||||||
useConfigMap, _, skip = isConfigFile(mountHost)
|
|
||||||
|
// 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 {
|
if skip {
|
||||||
log.Warnf("Skip file in path %s ", volume.Host)
|
log.Warnf("Skip file in path %s ", volume.Host)
|
||||||
continue
|
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
|
||||||
|
}
|
||||||
if volume.VolumeName == "" {
|
if volume.VolumeName == "" {
|
||||||
if useEmptyVolumes {
|
if volumeUseEmpty {
|
||||||
volumeName = strings.Replace(volume.PVCName, "claim", "empty", 1)
|
volumeName = strings.Replace(volume.PVCName, "claim", "empty", 1)
|
||||||
} else if useHostPath {
|
} else if volumeUseHostPath {
|
||||||
volumeName = strings.Replace(volume.PVCName, "claim", "hostpath", 1)
|
volumeName = strings.Replace(volume.PVCName, "claim", "hostpath", 1)
|
||||||
} else if useConfigMap {
|
} else if volumeUseConfigMap {
|
||||||
volumeName = strings.Replace(volume.PVCName, "claim", "cm", 1)
|
volumeName = strings.Replace(volume.PVCName, "claim", "cm", 1)
|
||||||
} else {
|
} else {
|
||||||
volumeName = volume.PVCName
|
volumeName = volume.PVCName
|
||||||
@ -1047,15 +1179,15 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) (
|
|||||||
// For PVC we will also create a PVC object and add to list
|
// For PVC we will also create a PVC object and add to list
|
||||||
var volsource *api.VolumeSource
|
var volsource *api.VolumeSource
|
||||||
|
|
||||||
if useEmptyVolumes {
|
if volumeUseEmpty {
|
||||||
volsource = k.ConfigEmptyVolumeSource("volume")
|
volsource = k.ConfigEmptyVolumeSource("volume")
|
||||||
} else if useHostPath {
|
} else if volumeUseHostPath {
|
||||||
source, err := k.ConfigHostPathVolumeSource(volume.Host)
|
source, err := k.ConfigHostPathVolumeSource(volume.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, errors.Wrap(err, "k.ConfigHostPathVolumeSource failed")
|
return nil, nil, nil, nil, errors.Wrap(err, "k.ConfigHostPathVolumeSource failed")
|
||||||
}
|
}
|
||||||
volsource = source
|
volsource = source
|
||||||
} else if useConfigMap {
|
} else if volumeUseConfigMap {
|
||||||
log.Debugf("Use configmap volume")
|
log.Debugf("Use configmap volume")
|
||||||
cm, err := k.IntiConfigMapFromFileOrDir(name, volumeName, volume.Host, service)
|
cm, err := k.IntiConfigMapFromFileOrDir(name, volumeName, volume.Host, service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1067,6 +1199,25 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) (
|
|||||||
if useSubPathMount(cm) {
|
if useSubPathMount(cm) {
|
||||||
volMount.SubPath = volsource.ConfigMap.Items[0].Path
|
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 {
|
} else {
|
||||||
volsource = k.ConfigPVCVolumeSource(volumeName, readonly)
|
volsource = k.ConfigPVCVolumeSource(volumeName, readonly)
|
||||||
if volume.VFrom == "" {
|
if volume.VFrom == "" {
|
||||||
@ -1087,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1136,6 +1292,13 @@ func (k *Kubernetes) ConfigEmptyVolumeSource(key string) *api.VolumeSource {
|
|||||||
func (k *Kubernetes) ConfigConfigMapVolumeSource(cmName string, targetPath string, cm *api.ConfigMap) *api.VolumeSource {
|
func (k *Kubernetes) ConfigConfigMapVolumeSource(cmName string, targetPath string, cm *api.ConfigMap) *api.VolumeSource {
|
||||||
s := api.ConfigMapVolumeSource{}
|
s := api.ConfigMapVolumeSource{}
|
||||||
s.Name = cmName
|
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) {
|
if useSubPathMount(cm) {
|
||||||
var keys []string
|
var keys []string
|
||||||
for k := range cm.Data {
|
for k := range cm.Data {
|
||||||
@ -1225,12 +1388,18 @@ func ConfigEnvs(service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]ap
|
|||||||
// Load up the environment variables
|
// Load up the environment variables
|
||||||
for _, v := range service.Environment {
|
for _, v := range service.Environment {
|
||||||
if !keysFromEnvFile[v.Name] {
|
if !keysFromEnvFile[v.Name] {
|
||||||
if strings.Contains(v.Value, "run/secrets") {
|
value := v.Value
|
||||||
v.Value = FormatResourceName(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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
envs = append(envs, api.EnvVar{
|
envs = append(envs, api.EnvVar{
|
||||||
Name: v.Name,
|
Name: v.Name,
|
||||||
Value: v.Value,
|
Value: value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1347,15 +1516,19 @@ func (k *Kubernetes) CreateWorkloadAndConfigMapObjects(name string, service kobj
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opt.CreateD || opt.Controller == DeploymentController {
|
if opt.CreateD || opt.Controller == DeploymentController {
|
||||||
objects = append(objects, k.InitD(name, service, replica))
|
objects = append(objects, k.InitD(name, service, replica, opt))
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt.CreateDS || opt.Controller == DaemonSetController {
|
if opt.CreateDS || opt.Controller == DaemonSetController {
|
||||||
objects = append(objects, k.InitDS(name, service))
|
objects = append(objects, k.InitDS(name, service, opt))
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt.Controller == StatefulStateController {
|
if opt.Controller == StatefulStateController {
|
||||||
objects = append(objects, k.InitSS(name, service, replica))
|
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)
|
||||||
@ -1392,7 +1565,7 @@ func (k *Kubernetes) createConfigMapFromComposeConfig(name string, service kobje
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitPod initializes Kubernetes Pod object
|
// InitPod initializes Kubernetes Pod object
|
||||||
func (k *Kubernetes) InitPod(name string, service kobject.ServiceConfig) *api.Pod {
|
func (k *Kubernetes) InitPod(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) *api.Pod {
|
||||||
pod := api.Pod{
|
pod := api.Pod{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: "Pod",
|
Kind: "Pod",
|
||||||
@ -1403,7 +1576,7 @@ func (k *Kubernetes) InitPod(name string, service kobject.ServiceConfig) *api.Po
|
|||||||
Labels: transformer.ConfigLabels(name),
|
Labels: transformer.ConfigLabels(name),
|
||||||
Annotations: transformer.ConfigAnnotations(service),
|
Annotations: transformer.ConfigAnnotations(service),
|
||||||
},
|
},
|
||||||
Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret),
|
Spec: k.InitPodSpec(name, service, opt),
|
||||||
}
|
}
|
||||||
return &pod
|
return &pod
|
||||||
}
|
}
|
||||||
@ -1613,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")
|
||||||
}
|
}
|
||||||
@ -1671,6 +1844,22 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
|
|||||||
allobjects = append(allobjects, objects...)
|
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)
|
sortedKeys := SortedKeys(komposeObject.ServiceConfigs)
|
||||||
for _, name := range sortedKeys {
|
for _, name := range sortedKeys {
|
||||||
service := komposeObject.ServiceConfigs[name]
|
service := komposeObject.ServiceConfigs[name]
|
||||||
@ -1680,6 +1869,12 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
|
|||||||
continue
|
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
|
var objects []runtime.Object
|
||||||
|
|
||||||
service.WithKomposeAnnotation = opt.WithKomposeAnnotation
|
service.WithKomposeAnnotation = opt.WithKomposeAnnotation
|
||||||
@ -1689,13 +1884,16 @@ 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)
|
cronJob := k.InitCJ(name, service, service.CronJobSchedule, service.CronJobConcurrencyPolicy, service.CronJobBackoffLimit, opt)
|
||||||
objects = append(objects, cronJob)
|
objects = append(objects, cronJob)
|
||||||
} else {
|
} else {
|
||||||
pod := k.InitPod(name, service)
|
pod := k.InitPod(name, service, opt)
|
||||||
objects = append(objects, pod)
|
objects = append(objects, pod)
|
||||||
}
|
}
|
||||||
envConfigMaps := k.PargeEnvFiletoConfigMaps(name, service, opt)
|
envConfigMaps := k.PargeEnvFiletoConfigMaps(name, service, opt)
|
||||||
@ -1707,7 +1905,7 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
|
|||||||
service.ServiceType = "Headless"
|
service.ServiceType = "Headless"
|
||||||
}
|
}
|
||||||
k.configKubeServiceAndIngressForService(service, name, &objects)
|
k.configKubeServiceAndIngressForService(service, name, &objects)
|
||||||
err := k.UpdateKubernetesObjects(name, service, opt, &objects)
|
err := k.UpdateKubernetesObjects(name, service, opt, &objects, &komposeObject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
|
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
|
||||||
}
|
}
|
||||||
@ -1763,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 {
|
||||||
|
|||||||
@ -611,7 +611,9 @@ func TestRestartOnFailure(t *testing.T) {
|
|||||||
func TestInitPodSpec(t *testing.T) {
|
func TestInitPodSpec(t *testing.T) {
|
||||||
name := "foo"
|
name := "foo"
|
||||||
k := Kubernetes{}
|
k := Kubernetes{}
|
||||||
result := k.InitPodSpec(name, newServiceConfig().Image, "")
|
service := newServiceConfig()
|
||||||
|
opt := kobject.ConvertOptions{}
|
||||||
|
result := k.InitPodSpec(name, service, opt)
|
||||||
if result.Containers[0].Name != "foo" && result.Containers[0].Image != "image" {
|
if result.Containers[0].Name != "foo" && result.Containers[0].Image != "image" {
|
||||||
t.Fatalf("Pod object not found")
|
t.Fatalf("Pod object not found")
|
||||||
}
|
}
|
||||||
@ -953,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"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -151,7 +151,7 @@ func initBuildConfig(name string, service kobject.ServiceConfig, repo string, br
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initDeploymentConfig initializes OpenShifts DeploymentConfig object
|
// initDeploymentConfig initializes OpenShifts DeploymentConfig object
|
||||||
func (o *OpenShift) initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int) *deployapi.DeploymentConfig {
|
func (o *OpenShift) initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int, opt kobject.ConvertOptions) *deployapi.DeploymentConfig {
|
||||||
containerName := []string{name}
|
containerName := []string{name}
|
||||||
|
|
||||||
// Properly add tags to the image name
|
// Properly add tags to the image name
|
||||||
@ -164,9 +164,14 @@ func (o *OpenShift) initDeploymentConfig(name string, service kobject.ServiceCon
|
|||||||
|
|
||||||
var podSpec corev1.PodSpec
|
var podSpec corev1.PodSpec
|
||||||
if len(service.Configs) > 0 {
|
if len(service.Configs) > 0 {
|
||||||
podSpec = o.InitPodSpecWithConfigMap(name, " ", service)
|
podSpec = o.InitPodSpecWithConfigMap(name, service, opt)
|
||||||
} else {
|
} else {
|
||||||
podSpec = o.InitPodSpec(name, " ", "")
|
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 = " "
|
||||||
}
|
}
|
||||||
|
|
||||||
dc := &deployapi.DeploymentConfig{
|
dc := &deployapi.DeploymentConfig{
|
||||||
@ -333,10 +338,10 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
if service.CronJobSchedule != "" {
|
if service.CronJobSchedule != "" {
|
||||||
cronJob := o.InitCJ(name, service, service.CronJobSchedule, service.CronJobConcurrencyPolicy, service.CronJobBackoffLimit)
|
cronJob := o.InitCJ(name, service, service.CronJobSchedule, service.CronJobConcurrencyPolicy, service.CronJobBackoffLimit, opt)
|
||||||
objects = append(objects, cronJob)
|
objects = append(objects, cronJob)
|
||||||
} else {
|
} else {
|
||||||
pod := o.InitPod(name, service)
|
pod := o.InitPod(name, service, opt)
|
||||||
objects = append(objects, pod)
|
objects = append(objects, pod)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,7 +351,7 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C
|
|||||||
objects = o.CreateWorkloadAndConfigMapObjects(name, service, opt)
|
objects = o.CreateWorkloadAndConfigMapObjects(name, service, opt)
|
||||||
|
|
||||||
if opt.CreateDeploymentConfig {
|
if opt.CreateDeploymentConfig {
|
||||||
objects = append(objects, o.initDeploymentConfig(name, service, replica)) // OpenShift DeploymentConfigs
|
objects = append(objects, o.initDeploymentConfig(name, service, replica, opt)) // OpenShift DeploymentConfigs
|
||||||
// create ImageStream after deployment (creating IS will trigger new deployment)
|
// create ImageStream after deployment (creating IS will trigger new deployment)
|
||||||
objects = append(objects, o.initImageStream(name, service, opt))
|
objects = append(objects, o.initImageStream(name, service, opt))
|
||||||
}
|
}
|
||||||
@ -426,7 +431,7 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := o.UpdateKubernetesObjects(name, service, opt, &objects)
|
err := o.UpdateKubernetesObjects(name, service, opt, &objects, &komposeObject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
|
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,9 +74,12 @@ func TestOpenShiftUpdateKubernetesObjects(t *testing.T) {
|
|||||||
o := OpenShift{}
|
o := OpenShift{}
|
||||||
serviceConfig := newServiceConfig()
|
serviceConfig := newServiceConfig()
|
||||||
opt := kobject.ConvertOptions{}
|
opt := kobject.ConvertOptions{}
|
||||||
|
komposeObject := kobject.KomposeObject{
|
||||||
|
ServiceConfigs: map[string]kobject.ServiceConfig{"foobar": serviceConfig},
|
||||||
|
}
|
||||||
|
|
||||||
object = append(object, o.initDeploymentConfig("foobar", serviceConfig, 3))
|
object = append(object, o.initDeploymentConfig("foobar", serviceConfig, 3, opt))
|
||||||
o.UpdateKubernetesObjects("foobar", serviceConfig, opt, &object)
|
o.UpdateKubernetesObjects("foobar", serviceConfig, opt, &object, &komposeObject)
|
||||||
|
|
||||||
for _, obj := range object {
|
for _, obj := range object {
|
||||||
switch tobj := obj.(type) {
|
switch tobj := obj.(type) {
|
||||||
@ -95,7 +98,8 @@ func TestOpenShiftUpdateKubernetesObjects(t *testing.T) {
|
|||||||
|
|
||||||
func TestInitDeploymentConfig(t *testing.T) {
|
func TestInitDeploymentConfig(t *testing.T) {
|
||||||
o := OpenShift{}
|
o := OpenShift{}
|
||||||
spec := o.initDeploymentConfig("foobar", newServiceConfig(), 1)
|
opt := kobject.ConvertOptions{}
|
||||||
|
spec := o.initDeploymentConfig("foobar", newServiceConfig(), 1, opt)
|
||||||
|
|
||||||
// Check that "foobar" is used correctly as a name
|
// Check that "foobar" is used correctly as a name
|
||||||
if spec.Spec.Template.Spec.Containers[0].Name != "foobar" {
|
if spec.Spec.Template.Spec.Containers[0].Name != "foobar" {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user