Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pkg/chart/adaptor/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package adaptor
import (
"encoding/json"
"fmt"
"strings"

apiGitOpsBean "github.com/devtron-labs/devtron/api/bean/gitOps"
"github.com/devtron-labs/devtron/internal/util"
"github.com/devtron-labs/devtron/pkg/chart/bean"
chartRepoRepository "github.com/devtron-labs/devtron/pkg/chartRepo/repository"
bean2 "github.com/devtron-labs/devtron/pkg/deployment/common/bean"
util2 "github.com/devtron-labs/devtron/util"
"strings"
)

// ChartAdaptor converts db object chartRepoRepository.Chart to bean.TemplateRequest
Expand Down
1 change: 1 addition & 0 deletions pkg/chart/bean/bean.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package bean

import (
"encoding/json"

"github.com/devtron-labs/devtron/internal/sql/models"
bean2 "github.com/devtron-labs/devtron/pkg/pipeline/bean"
)
Expand Down
24 changes: 15 additions & 9 deletions pkg/generateManifest/DeploymentTemplateService.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ package generateManifest
import (
"context"
"fmt"
"net/http"
"regexp"
"strconv"
"sync"

"github.com/caarlos0/env"
"github.com/devtron-labs/common-lib/utils/k8s"
"github.com/devtron-labs/devtron/api/helm-app/gRPC"
Expand Down Expand Up @@ -53,11 +58,7 @@ import (
"golang.org/x/exp/maps"
chart2 "helm.sh/helm/v3/pkg/chart"
"k8s.io/utils/pointer"
"net/http"
"regexp"
"sigs.k8s.io/yaml"
"strconv"
"sync"
)

// TODO: Prakash, move this interface to pkg/deployment/manifest/deploymentTemplate, both are same
Expand Down Expand Up @@ -488,7 +489,7 @@ func (impl DeploymentTemplateServiceImpl) GenerateManifest(ctx context.Context,
if len(request.DeploymentAppName) != 0 {
releaseName = request.DeploymentAppName
}
mergedValuesYaml := impl.patchReleaseAttributes(request, valuesYaml)
mergedValuesYaml := impl.patchReleaseAttributes(request, valuesYaml, refChartPath)
installReleaseRequest := &gRPC.InstallReleaseRequest{
AppName: request.AppName,
ChartName: refChart.Name,
Expand Down Expand Up @@ -530,7 +531,7 @@ func (impl DeploymentTemplateServiceImpl) GenerateManifest(ctx context.Context,
return response, nil
}

func (impl DeploymentTemplateServiceImpl) patchReleaseAttributes(request *DeploymentTemplateRequest, valuesYaml string) (mergedValuesYaml string) {
func (impl DeploymentTemplateServiceImpl) patchReleaseAttributes(request *DeploymentTemplateRequest, valuesYaml string, refChartPath string) (mergedValuesYaml string) {
mergedValuesYaml = valuesYaml

valuesJsonByte, err := yaml.YAMLToJSON([]byte(valuesYaml))
Expand All @@ -539,14 +540,13 @@ func (impl DeploymentTemplateServiceImpl) patchReleaseAttributes(request *Deploy
return
}

chartDto, err := impl.chartReadService.GetByAppIdAndChartRefId(request.AppId, request.ChartRefId)
imageDescriptorTemplate, err := impl.resolveImageDescriptorTemplate(request.AppId, request.ChartRefId, refChartPath)
if err != nil {
impl.Logger.Errorw("error in getting the chart using appId and chartRefId", "appId", request.AppId, "chartRefId", request.ChartRefId, "err", err)
return
}

releaseAttributeJson, err := app.NewReleaseAttributes("", "", request.PipelineName, "",
request.AppId, request.EnvId, 0, pointer.Bool(false)).RenderJson(chartDto.ImageDescriptorTemplate)
request.AppId, request.EnvId, 0, pointer.Bool(false)).RenderJson(imageDescriptorTemplate)

if err != nil {
impl.Logger.Errorw("error in rendering release attributes into image descriptor template ", "releaseAttributeJson", releaseAttributeJson, "err", err)
Expand All @@ -566,6 +566,12 @@ func (impl DeploymentTemplateServiceImpl) patchReleaseAttributes(request *Deploy

mergedValuesYaml = string(mergedYamlBytes)

// Read release-values.yaml straight from the reference chart dir so the manifest
// can be rendered before an app-level chart is saved (chartDto.ReleaseOverride is
// only populated post-save).
releaseOverrideJson := impl.readReleaseValuesJsonFromRefChart(refChartPath)
mergedValuesYaml = impl.mergeReleaseOverrideIntoValuesYaml(mergedValuesYaml, releaseOverrideJson)

return mergedValuesYaml
}

Expand Down
121 changes: 121 additions & 0 deletions pkg/generateManifest/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,138 @@ package generateManifest

import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/devtron-labs/common-lib/utils/yaml"
"github.com/devtron-labs/devtron/api/helm-app/gRPC"
"github.com/devtron-labs/devtron/internal/sql/repository/app"
"github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig"
clusterBean "github.com/devtron-labs/devtron/pkg/cluster/bean"
"github.com/devtron-labs/devtron/pkg/cluster/environment/repository"
"github.com/go-pg/pg"
"go.opentelemetry.io/otel"
"golang.org/x/exp/maps"
k8sYaml "sigs.k8s.io/yaml"
)

const (
releaseValuesYamlFile = "release-values.yaml"
releaseValuesYmlFile = "release-values.yml"
imageDescriptorTemplateFile = ".image_descriptor_template.json"
)

// readReleaseValuesJsonFromRefChart looks for release-values.yaml / release-values.yml
// at the root of the reference chart directory and returns its content converted to
// JSON. Returns an empty string (without erroring the caller) when the file is absent
// or unreadable — not every chart defines release overrides, and manifest generation
// must still proceed. This mirrors the file-discovery logic in
// ChartTemplateServiceImpl.getValues so behaviour matches the save-time path.
func (impl DeploymentTemplateServiceImpl) readReleaseValuesJsonFromRefChart(refChartPath string) string {
data, ok := impl.readFileFromRefChart(refChartPath, releaseValuesYamlFile, releaseValuesYmlFile)
if !ok {
return ""
}
jsonBytes, err := k8sYaml.YAMLToJSON(data)
if err != nil {
impl.Logger.Errorw("error in converting release-values yaml to json", "refChartPath", refChartPath, "err", err)
return ""
}
return string(jsonBytes)
}

// readImageDescriptorTemplateFromRefChart reads .image_descriptor_template.json from
// the reference chart directory. Used when the chart hasn't been saved to the DB yet
// and chartDto.ImageDescriptorTemplate isn't available. Returns an empty string on
// missing file / read error — RenderJson handles an empty template gracefully.
func (impl DeploymentTemplateServiceImpl) readImageDescriptorTemplateFromRefChart(refChartPath string) string {
data, ok := impl.readFileFromRefChart(refChartPath, imageDescriptorTemplateFile)
if !ok {
return ""
}
return string(data)
}

// readFileFromRefChart scans refChartPath (non-recursive) for the first file whose
// name (case-insensitive) matches one of fileNames and returns its contents. ok=false
// means the path was empty, unreadable, or the file was absent — all logged at the
// appropriate level by the caller's context.
func (impl DeploymentTemplateServiceImpl) readFileFromRefChart(refChartPath string, fileNames ...string) (data []byte, ok bool) {
if len(refChartPath) == 0 {
return nil, false
}
entries, err := os.ReadDir(refChartPath)
if err != nil {
impl.Logger.Errorw("error in reading ref chart dir", "refChartPath", refChartPath, "err", err)
return nil, false
}
targets := make(map[string]struct{}, len(fileNames))
for _, name := range fileNames {
targets[strings.ToLower(name)] = struct{}{}
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
if _, match := targets[strings.ToLower(entry.Name())]; !match {
continue
}
path := filepath.Clean(filepath.Join(refChartPath, entry.Name()))
contents, err := os.ReadFile(path)
if err != nil {
impl.Logger.Errorw("error in reading file from ref chart", "path", path, "err", err)
return nil, false
}
return contents, true
}
return nil, false
}

// resolveImageDescriptorTemplate returns the image descriptor template for the app's
// chart. It prefers the saved chart in DB; if no chart has been saved for this
// (appId, chartRefId) pair (pg.ErrNoRows), it falls back to the reference chart's
// .image_descriptor_template.json on disk so manifest rendering works before save.
// Any other DB error is returned to the caller.
func (impl DeploymentTemplateServiceImpl) resolveImageDescriptorTemplate(appId, chartRefId int, refChartPath string) (string, error) {
chartDto, err := impl.chartReadService.GetByAppIdAndChartRefId(appId, chartRefId)
if err == nil {
return chartDto.ImageDescriptorTemplate, nil
}
if !errors.Is(err, pg.ErrNoRows) {
impl.Logger.Errorw("error in getting chart by appId and chartRefId", "appId", appId, "chartRefId", chartRefId, "err", err)
return "", err
}
return impl.readImageDescriptorTemplateFromRefChart(refChartPath), nil
}

// mergeReleaseOverrideIntoValuesYaml merges the chart's ReleaseOverride JSON
// on top of the given values YAML. On any failure the input valuesYaml is
// returned unchanged so template rendering can still proceed.
func (impl DeploymentTemplateServiceImpl) mergeReleaseOverrideIntoValuesYaml(valuesYaml, releaseOverrideJson string) string {
if len(releaseOverrideJson) == 0 || releaseOverrideJson == "{}" {
return valuesYaml
}
valuesJsonByte, err := k8sYaml.YAMLToJSON([]byte(valuesYaml))
if err != nil {
impl.Logger.Errorw("error in converting values yaml to json", "err", err)
return valuesYaml
}
mergedJsonBytes, err := impl.mergeUtil.JsonPatch(valuesJsonByte, []byte(releaseOverrideJson))
if err != nil {
impl.Logger.Errorw("error in merging releaseOverride into values yaml", "releaseOverrideJson", releaseOverrideJson, "err", err)
return valuesYaml
}
mergedYamlBytes, err := k8sYaml.JSONToYAML(mergedJsonBytes)
if err != nil {
impl.Logger.Errorw("error in converting merged json to yaml", "err", err)
return valuesYaml
}
return string(mergedYamlBytes)
}

func (impl DeploymentTemplateServiceImpl) constructRotatePodResponse(templateChartResponse []*gRPC.TemplateChartResponse, appNameToId map[string]int, environment *repository.Environment) (*RestartPodResponse, error) {
appIdToResourceIdentifier := make(map[int]*ResourceIdentifierResponse)
for _, tcResp := range templateChartResponse {
Expand Down
Loading