mirror of
https://github.com/helmfile/helmfile.git
synced 2026-09-30 12:34:23 +02:00
When helmfile.d contains multiple release files and one release has a local chart dependency (e.g. chart: ../chart), the dependency path was passed to DirectoryExistsAt without normalizing against basePath. This caused the path to be resolved against CWD instead of the helmfile directory, so the local chart was not detected and helmfile tried to resolve it as a remote repo, failing with: 'failed reading adhoc dependencies: no helm list entry found for repository' Fixes #2596 Signed-off-by: yxxhero <aiopsclub@163.com>
728 lines
21 KiB
Go
728 lines
21 KiB
Go
package state
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/helmfile/chartify"
|
|
"helm.sh/helm/v4/pkg/storage/driver"
|
|
|
|
"github.com/helmfile/helmfile/pkg/filesystem"
|
|
"github.com/helmfile/helmfile/pkg/helmexec"
|
|
"github.com/helmfile/helmfile/pkg/kubedog"
|
|
"github.com/helmfile/helmfile/pkg/remote"
|
|
"github.com/helmfile/helmfile/pkg/resource"
|
|
"github.com/helmfile/helmfile/pkg/tmpl"
|
|
)
|
|
|
|
type Dependency struct {
|
|
Chart string `yaml:"chart"`
|
|
Version string `yaml:"version"`
|
|
Alias string `yaml:"alias"`
|
|
}
|
|
|
|
func (st *HelmState) appendHelmXFlags(flags []string, release *ReleaseSpec) []string {
|
|
for _, adopt := range release.Adopt {
|
|
flags = append(flags, "--adopt", adopt)
|
|
}
|
|
|
|
return flags
|
|
}
|
|
|
|
func formatLabels(labels map[string]string) string {
|
|
var labelsList, keys []string
|
|
for k := range labels {
|
|
if k == "" || slices.Contains(driver.GetSystemLabels(), k) {
|
|
continue
|
|
}
|
|
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
if len(keys) == 0 {
|
|
return ""
|
|
}
|
|
|
|
for _, k := range keys {
|
|
val := labels[k]
|
|
labelsList = append(labelsList, fmt.Sprintf("%s=%s", k, val))
|
|
}
|
|
return strings.Join(labelsList, ",")
|
|
}
|
|
|
|
// append labels flags to helm flags, starting from helm v3.13.0
|
|
func (st *HelmState) appendLabelsFlags(flags []string, helm helmexec.Interface, release *ReleaseSpec, syncReleaseLabels bool) []string {
|
|
if !helm.IsVersionAtLeast("3.13.0") {
|
|
return flags
|
|
}
|
|
isSyncReleaseLabels := false
|
|
switch {
|
|
// Check if SyncReleaseLabels is true in the release spec.
|
|
case release.SyncReleaseLabels != nil && *release.SyncReleaseLabels:
|
|
isSyncReleaseLabels = true
|
|
// Check if syncReleaseLabels argument is true.
|
|
case syncReleaseLabels:
|
|
isSyncReleaseLabels = true
|
|
// Check if SyncReleaseLabels is true in HelmDefaults.
|
|
case st.HelmDefaults.SyncReleaseLabels != nil && *st.HelmDefaults.SyncReleaseLabels:
|
|
isSyncReleaseLabels = true
|
|
}
|
|
if isSyncReleaseLabels {
|
|
labels := formatLabels(release.Labels)
|
|
if labels != "" {
|
|
flags = append(flags, "--labels", labels)
|
|
}
|
|
}
|
|
return flags
|
|
}
|
|
|
|
// append post-renderer flags to helm flags
|
|
func (st *HelmState) appendPostRenderFlags(flags []string, release *ReleaseSpec, postRenderer string, helm helmexec.Interface) []string {
|
|
var rendererPath string
|
|
switch {
|
|
// postRenderer arg comes from cmd flag.
|
|
case release.PostRenderer != nil && *release.PostRenderer != "":
|
|
rendererPath = *release.PostRenderer
|
|
case postRenderer != "":
|
|
rendererPath = postRenderer
|
|
case st.HelmDefaults.PostRenderer != nil && *st.HelmDefaults.PostRenderer != "":
|
|
rendererPath = *st.HelmDefaults.PostRenderer
|
|
}
|
|
|
|
if rendererPath != "" {
|
|
// For Helm 4, convert the bash script path to a plugin name
|
|
if helm != nil && !helm.IsHelm3() {
|
|
// Check if this is a bash script path that needs conversion
|
|
if strings.HasSuffix(rendererPath, ".bash") || strings.HasSuffix(rendererPath, ".sh") {
|
|
// Extract the base name (e.g., "add-cm1" from "../../postrenderers/add-cm1.bash")
|
|
baseName := filepath.Base(rendererPath)
|
|
baseName = strings.TrimSuffix(baseName, filepath.Ext(baseName))
|
|
|
|
// For Helm 4, use just the plugin name
|
|
// From: ../../postrenderers/add-cm1.bash
|
|
// To: add-cm1 (assuming the plugin is installed with this name)
|
|
rendererPath = baseName
|
|
}
|
|
}
|
|
flags = append(flags, "--post-renderer", rendererPath)
|
|
}
|
|
return flags
|
|
}
|
|
|
|
// append post-renderer-args flags to helm flags
|
|
func (st *HelmState) appendPostRenderArgsFlags(flags []string, release *ReleaseSpec, postRendererArgs []string) ([]string, error) {
|
|
postRendererArgsFlags := []string{}
|
|
switch {
|
|
case len(release.PostRendererArgs) != 0:
|
|
postRendererArgsFlags = release.PostRendererArgs
|
|
case len(postRendererArgs) != 0:
|
|
postRendererArgsFlags = postRendererArgs
|
|
case len(st.HelmDefaults.PostRendererArgs) != 0:
|
|
rendered, err := st.renderPostRendererArgs(release, st.HelmDefaults.PostRendererArgs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
postRendererArgsFlags = rendered
|
|
}
|
|
for _, arg := range postRendererArgsFlags {
|
|
if arg != "" {
|
|
flags = append(flags, "--post-renderer-args="+arg)
|
|
}
|
|
}
|
|
return flags, nil
|
|
}
|
|
|
|
func (st *HelmState) renderPostRendererArgs(release *ReleaseSpec, args []string) ([]string, error) {
|
|
vals := st.RenderedValues
|
|
if vals == nil {
|
|
vals = make(map[string]any)
|
|
}
|
|
|
|
fs := st.fs
|
|
if fs == nil {
|
|
fs = filesystem.DefaultFileSystem()
|
|
}
|
|
|
|
tmplData := st.createReleaseTemplateData(release, vals)
|
|
renderer := tmpl.NewFileRenderer(fs, st.basePath, tmplData)
|
|
|
|
result := make([]string, 0, len(args))
|
|
for _, arg := range args {
|
|
rendered, err := renderer.RenderTemplateContentToString([]byte(arg))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed rendering postRendererArg %q for release %q: %w", arg, release.Name, err)
|
|
}
|
|
result = append(result, rendered)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// append skip-schema-validation flags to helm flags
|
|
func (st *HelmState) appendSkipSchemaValidationFlags(flags []string, release *ReleaseSpec, skipSchemaValidation bool) []string {
|
|
if st.shouldSkipSchemaValidation(release, skipSchemaValidation) {
|
|
flags = append(flags, "--skip-schema-validation")
|
|
}
|
|
return flags
|
|
}
|
|
|
|
func (st *HelmState) shouldSkipSchemaValidation(release *ReleaseSpec, skipSchemaValidation bool) bool {
|
|
switch {
|
|
// Check if SkipSchemaValidation is true in the release spec.
|
|
case release.SkipSchemaValidation != nil && *release.SkipSchemaValidation:
|
|
return true
|
|
// Check if skipSchemaValidation argument is true.
|
|
case skipSchemaValidation:
|
|
return true
|
|
// Check if SkipSchemaValidation is true in HelmDefaults.
|
|
case st.HelmDefaults.SkipSchemaValidation != nil && *st.HelmDefaults.SkipSchemaValidation:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// append suppress-output-line-regex flags to helm diff flags
|
|
func (st *HelmState) appendSuppressOutputLineRegexFlags(flags []string, release *ReleaseSpec, suppressOutputLineRegex []string) []string {
|
|
suppressOutputLineRegexFlags := []string{}
|
|
switch {
|
|
case len(release.SuppressOutputLineRegex) != 0:
|
|
suppressOutputLineRegexFlags = release.SuppressOutputLineRegex
|
|
case len(suppressOutputLineRegex) != 0:
|
|
suppressOutputLineRegexFlags = suppressOutputLineRegex
|
|
case len(st.HelmDefaults.SuppressOutputLineRegex) != 0:
|
|
suppressOutputLineRegexFlags = st.HelmDefaults.SuppressOutputLineRegex
|
|
}
|
|
for _, arg := range suppressOutputLineRegexFlags {
|
|
if arg != "" {
|
|
flags = append(flags, "--suppress-output-line-regex", arg)
|
|
}
|
|
}
|
|
return flags
|
|
}
|
|
|
|
func (st *HelmState) appendWaitForJobsFlags(flags []string, release *ReleaseSpec, ops *SyncOpts) []string {
|
|
if st.shouldUseKubedog(release, ops) {
|
|
return flags
|
|
}
|
|
|
|
switch {
|
|
case release.WaitForJobs != nil && *release.WaitForJobs:
|
|
flags = append(flags, "--wait-for-jobs")
|
|
case ops != nil && ops.WaitForJobs:
|
|
flags = append(flags, "--wait-for-jobs")
|
|
case release.WaitForJobs == nil && st.HelmDefaults.WaitForJobs:
|
|
flags = append(flags, "--wait-for-jobs")
|
|
}
|
|
|
|
return flags
|
|
}
|
|
|
|
func (st *HelmState) shouldUseKubedog(release *ReleaseSpec, ops *SyncOpts) bool {
|
|
return st.getTrackMode(release, ops) == string(kubedog.TrackModeKubedog)
|
|
}
|
|
|
|
func (st *HelmState) shouldFailOnTrackError(release *ReleaseSpec, ops *SyncOpts) bool {
|
|
if release.TrackFailOnError != nil {
|
|
return *release.TrackFailOnError
|
|
}
|
|
if ops != nil {
|
|
return ops.TrackFailOnError
|
|
}
|
|
return false
|
|
}
|
|
|
|
// trackReleaseIfEnabled performs kubedog tracking for a release if trackMode is "kubedog".
|
|
// It returns a ReleaseError if tracking fails and shouldFailOnTrackError is true.
|
|
// The caller is responsible for mutating affectedReleases when needed.
|
|
func (st *HelmState) trackReleaseIfEnabled(ctx context.Context, release *ReleaseSpec, helm helmexec.Interface, opts *SyncOpts) *ReleaseError {
|
|
if !st.shouldUseKubedog(release, opts) {
|
|
return nil
|
|
}
|
|
if trackErr := st.trackWithKubedog(ctx, release, helm, opts); trackErr != nil {
|
|
st.logger.Warnf("kubedog tracking failed for release %s: %v", release.Name, trackErr)
|
|
if st.shouldFailOnTrackError(release, opts) {
|
|
return newReleaseFailedError(release, trackErr)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (st *HelmState) getTrackMode(release *ReleaseSpec, ops *SyncOpts) string {
|
|
trackMode := release.TrackMode
|
|
if trackMode == "" && ops != nil && ops.TrackMode != "" {
|
|
trackMode = ops.TrackMode
|
|
}
|
|
if trackMode == "" {
|
|
trackMode = st.HelmDefaults.TrackMode
|
|
}
|
|
if trackMode == "" {
|
|
trackMode = string(kubedog.TrackModeHelm)
|
|
}
|
|
return trackMode
|
|
}
|
|
|
|
func (st *HelmState) appendWaitFlags(flags []string, helm helmexec.Interface, release *ReleaseSpec, ops *SyncOpts) []string {
|
|
if st.shouldUseKubedog(release, ops) {
|
|
return flags
|
|
}
|
|
|
|
shouldWait := false
|
|
switch {
|
|
case release.Wait != nil && *release.Wait:
|
|
shouldWait = true
|
|
case ops != nil && ops.Wait:
|
|
shouldWait = true
|
|
case release.Wait == nil && st.HelmDefaults.Wait:
|
|
shouldWait = true
|
|
}
|
|
|
|
if shouldWait {
|
|
trackMode := st.getTrackMode(release, ops)
|
|
if trackMode == string(kubedog.TrackModeHelmLegacy) {
|
|
if helm != nil && helm.IsHelm4() {
|
|
flags = append(flags, "--wait=legacy")
|
|
} else {
|
|
if st.logger != nil {
|
|
st.logger.Warnf("trackMode 'helm-legacy' requires Helm v4, falling back to regular --wait for release %s", release.Name)
|
|
}
|
|
flags = append(flags, "--wait")
|
|
}
|
|
} else {
|
|
flags = append(flags, "--wait")
|
|
}
|
|
}
|
|
|
|
return flags
|
|
}
|
|
|
|
// append post-renderer flags to helm flags
|
|
func (st *HelmState) appendCascadeFlags(flags []string, helm helmexec.Interface, release *ReleaseSpec, cascade string) []string {
|
|
// see https://github.com/helm/helm/releases/tag/v3.12.1
|
|
if !helm.IsVersionAtLeast("3.12.1") {
|
|
return flags
|
|
}
|
|
switch {
|
|
// postRenderer arg comes from cmd flag.
|
|
case release.Cascade != nil && *release.Cascade != "":
|
|
flags = append(flags, "--cascade", *release.Cascade)
|
|
case cascade != "":
|
|
flags = append(flags, "--cascade", cascade)
|
|
case st.HelmDefaults.Cascade != nil && *st.HelmDefaults.Cascade != "":
|
|
flags = append(flags, "--cascade", *st.HelmDefaults.Cascade)
|
|
}
|
|
return flags
|
|
}
|
|
|
|
// append hide-notes flags to helm flags
|
|
func (st *HelmState) appendHideNotesFlags(flags []string, helm helmexec.Interface, ops *SyncOpts) []string {
|
|
if ops == nil {
|
|
return flags
|
|
}
|
|
// see https://github.com/helm/helm/releases/tag/v3.16.0
|
|
if !helm.IsVersionAtLeast("3.16.0") {
|
|
return flags
|
|
}
|
|
switch {
|
|
case ops.HideNotes:
|
|
flags = append(flags, "--hide-notes")
|
|
}
|
|
return flags
|
|
}
|
|
|
|
// append take-ownership flags to helm flags
|
|
func (st *HelmState) appendTakeOwnershipFlagsForUpgrade(flags []string, helm helmexec.Interface, release *ReleaseSpec, takeOwnership bool) []string {
|
|
// see https://github.com/helm/helm/releases/tag/v3.17.0
|
|
if !helm.IsVersionAtLeast("3.17.0") {
|
|
return flags
|
|
}
|
|
switch {
|
|
case release.TakeOwnership != nil && *release.TakeOwnership:
|
|
flags = append(flags, "--take-ownership")
|
|
case takeOwnership:
|
|
flags = append(flags, "--take-ownership")
|
|
case st.HelmDefaults.TakeOwnership != nil && *st.HelmDefaults.TakeOwnership:
|
|
flags = append(flags, "--take-ownership")
|
|
}
|
|
return flags
|
|
}
|
|
|
|
// append show-only flags to helm flags
|
|
func (st *HelmState) appendShowOnlyFlags(flags []string, showOnly []string) []string {
|
|
showOnlyFlags := []string{}
|
|
if len(showOnly) != 0 {
|
|
showOnlyFlags = showOnly
|
|
}
|
|
for _, arg := range showOnlyFlags {
|
|
if arg != "" {
|
|
flags = append(flags, "--show-only", arg)
|
|
}
|
|
}
|
|
return flags
|
|
}
|
|
|
|
type Chartify struct {
|
|
Opts *chartify.ChartifyOpts
|
|
Clean func()
|
|
NeedsChartifyForLocalDir bool
|
|
}
|
|
|
|
func (st *HelmState) downloadChartWithGoGetter(r *ReleaseSpec) (string, error) {
|
|
var pathElems []string
|
|
|
|
if r.Namespace != "" {
|
|
pathElems = append(pathElems, r.Namespace)
|
|
}
|
|
|
|
if r.KubeContext != "" {
|
|
pathElems = append(pathElems, r.KubeContext)
|
|
}
|
|
|
|
pathElems = append(pathElems, r.Name)
|
|
|
|
cacheDir := filepath.Join(pathElems...)
|
|
|
|
return st.goGetterChart(r.Chart, r.Directory, cacheDir, r.ForceGoGetter)
|
|
}
|
|
|
|
func (st *HelmState) goGetterChart(chart, dir, cacheDir string, force bool) (string, error) {
|
|
if dir != "" && chart == "" {
|
|
chart = dir
|
|
}
|
|
|
|
_, err := remote.Parse(chart)
|
|
if err != nil {
|
|
if force {
|
|
return "", fmt.Errorf("Parsing url from dir failed due to error %q.\nContinuing the process assuming this is a regular Helm chart or a local dir.", err.Error())
|
|
}
|
|
} else {
|
|
r := remote.NewRemote(st.logger, "", st.fs)
|
|
|
|
fetchedDir, err := r.Fetch(chart, cacheDir)
|
|
if err != nil {
|
|
return "", fmt.Errorf("fetching %q: %v", chart, err)
|
|
}
|
|
|
|
chart = fetchedDir
|
|
}
|
|
|
|
return chart, nil
|
|
}
|
|
|
|
func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSpec, chart string, workerIndex int) (*Chartify, func(), error) {
|
|
c := &Chartify{
|
|
Opts: &chartify.ChartifyOpts{
|
|
WorkaroundOutputDirIssue: true,
|
|
EnableKustomizeAlphaPlugins: true,
|
|
ChartVersion: release.Version,
|
|
Namespace: release.Namespace,
|
|
ID: ReleaseToID(release),
|
|
},
|
|
}
|
|
|
|
var filesNeedCleaning []string
|
|
|
|
clean := func() {
|
|
st.removeFiles(filesNeedCleaning)
|
|
}
|
|
|
|
var shouldRun bool
|
|
|
|
dir := chart
|
|
if !filepath.IsAbs(chart) {
|
|
dir = filepath.Join(st.basePath, chart)
|
|
}
|
|
if stat, _ := os.Stat(dir); stat != nil && stat.IsDir() {
|
|
if exists, err := st.fs.FileExists(filepath.Join(dir, "Chart.yaml")); err == nil && !exists {
|
|
shouldRun = true
|
|
c.NeedsChartifyForLocalDir = true
|
|
}
|
|
}
|
|
|
|
for _, d := range release.Dependencies {
|
|
chart := d.Chart
|
|
normalizedChart := normalizeChart(st.basePath, chart)
|
|
if st.fs.DirectoryExistsAt(normalizedChart) {
|
|
var err error
|
|
|
|
// Otherwise helm-dependency-up on the temporary chart generated by chartify ends up errors like:
|
|
// Error: directory /tmp/chartify945964195/myapp-57fb4495cf/test/integration/charts/httpbin not found]
|
|
// which is due to that the temporary chart is generated outside of the current working directory/basePath,
|
|
// and therefore the relative path in `chart` points to somewhere inexistent.
|
|
chart, err = filepath.Abs(filepath.Join(st.basePath, chart))
|
|
if err != nil {
|
|
return nil, clean, err
|
|
}
|
|
} else if rewritten, ok := st.resolveOCIAdhocDepChart(d.Chart); ok {
|
|
st.logger.Debugf("ad-hoc dependency %q rewritten to %q (matched OCI repo entry)", d.Chart, rewritten)
|
|
chart = rewritten
|
|
}
|
|
|
|
c.Opts.AdhocChartDependencies = append(c.Opts.AdhocChartDependencies, chartify.ChartDependency{
|
|
Alias: d.Alias,
|
|
Chart: chart,
|
|
Version: d.Version,
|
|
})
|
|
|
|
shouldRun = true
|
|
}
|
|
|
|
jsonPatches := release.JSONPatches
|
|
if len(jsonPatches) > 0 {
|
|
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, jsonPatches)
|
|
if err != nil {
|
|
return nil, clean, err
|
|
}
|
|
|
|
filesNeedCleaning = append(filesNeedCleaning, generatedFiles...)
|
|
|
|
c.Opts.JsonPatches = append(c.Opts.JsonPatches, generatedFiles...)
|
|
|
|
shouldRun = true
|
|
}
|
|
|
|
strategicMergePatches := release.StrategicMergePatches
|
|
if len(strategicMergePatches) > 0 {
|
|
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, strategicMergePatches)
|
|
if err != nil {
|
|
return nil, clean, err
|
|
}
|
|
|
|
c.Opts.StrategicMergePatches = append(c.Opts.StrategicMergePatches, generatedFiles...)
|
|
|
|
filesNeedCleaning = append(filesNeedCleaning, generatedFiles...)
|
|
|
|
shouldRun = true
|
|
}
|
|
|
|
transformers := release.Transformers
|
|
if len(transformers) > 0 {
|
|
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, transformers)
|
|
if err != nil {
|
|
return nil, clean, err
|
|
}
|
|
|
|
c.Opts.Transformers = append(c.Opts.Transformers, generatedFiles...)
|
|
|
|
filesNeedCleaning = append(filesNeedCleaning, generatedFiles...)
|
|
|
|
shouldRun = true
|
|
}
|
|
|
|
if release.ForceNamespace != "" {
|
|
c.Opts.OverrideNamespace = release.ForceNamespace
|
|
|
|
shouldRun = true
|
|
}
|
|
|
|
if shouldRun {
|
|
st.logger.Debugf("Chartify process for %s", dir)
|
|
generatedFiles, err := st.generateValuesFiles(helm, release, workerIndex)
|
|
if err != nil {
|
|
return nil, clean, err
|
|
}
|
|
|
|
filesNeedCleaning = append(filesNeedCleaning, generatedFiles...)
|
|
|
|
c.Opts.ValuesFiles = generatedFiles
|
|
setFlags, err := st.setFlags(release.SetValues)
|
|
if err != nil {
|
|
return nil, clean, fmt.Errorf("rendering set value entry for release %s: %v", release.Name, err)
|
|
}
|
|
c.Opts.SetFlags = setFlags
|
|
c.Opts.TemplateData = st.newReleaseTemplateData(release)
|
|
c.Opts.TemplateFuncs = st.newReleaseTemplateFuncMap(dir)
|
|
|
|
return c, clean, nil
|
|
}
|
|
|
|
return nil, clean, nil
|
|
}
|
|
|
|
func (st *HelmState) trackWithKubedog(ctx context.Context, release *ReleaseSpec, helm helmexec.Interface, ops *SyncOpts) error {
|
|
timeout := 5 * time.Minute
|
|
if release.TrackTimeout != nil && *release.TrackTimeout > 0 {
|
|
timeout = time.Duration(*release.TrackTimeout) * time.Second
|
|
} else if ops != nil && ops.TrackTimeout > 0 {
|
|
timeout = time.Duration(ops.TrackTimeout) * time.Second
|
|
}
|
|
|
|
trackLogs := release.TrackLogs != nil && *release.TrackLogs
|
|
if release.TrackLogs == nil && ops != nil {
|
|
trackLogs = ops.TrackLogs
|
|
}
|
|
|
|
filterConfig := &resource.FilterConfig{
|
|
TrackKinds: release.TrackKinds,
|
|
SkipKinds: release.SkipKinds,
|
|
TrackResources: convertTrackResources(release.TrackResources),
|
|
}
|
|
|
|
kubeContext := st.getKubeContext(release)
|
|
|
|
trackOpts := kubedog.NewTrackOptions().
|
|
WithTimeout(timeout).
|
|
WithLogs(trackLogs).
|
|
WithFilterConfig(filterConfig)
|
|
|
|
tracker, err := kubedog.NewTracker(&kubedog.TrackerConfig{
|
|
Logger: st.logger,
|
|
Namespace: release.Namespace,
|
|
KubeContext: kubeContext,
|
|
Kubeconfig: st.kubeconfig,
|
|
TrackOptions: trackOpts,
|
|
KubedogQPS: release.KubedogQPS,
|
|
KubedogBurst: release.KubedogBurst,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create kubedog tracker: %w", err)
|
|
}
|
|
|
|
resources, err := st.getReleaseResources(ctx, release, helm)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get release resources: %w", err)
|
|
}
|
|
|
|
if len(resources) == 0 {
|
|
st.logger.Infof("No trackable resources found for release %s", release.Name)
|
|
return nil
|
|
}
|
|
|
|
st.logger.Infof("Tracking %d resources from release %s with kubedog", len(resources), release.Name)
|
|
|
|
if err := tracker.TrackResources(ctx, resources); err != nil {
|
|
return fmt.Errorf("kubedog tracking failed for release %s: %w", release.Name, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (st *HelmState) getReleaseResources(_ context.Context, release *ReleaseSpec, helm helmexec.Interface) ([]*resource.Resource, error) {
|
|
st.logger.Debugf("Getting resources for release %s", release.Name)
|
|
|
|
manifest, namespace, err := st.getReleaseManifest(release, helm)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get release manifest: %w", err)
|
|
}
|
|
|
|
if len(manifest) == 0 {
|
|
st.logger.Infof("No manifest found for release %s", release.Name)
|
|
return nil, nil
|
|
}
|
|
|
|
defaultNs := namespace
|
|
if defaultNs == "" {
|
|
defaultNs = "default"
|
|
}
|
|
|
|
resources, err := resource.ParseManifest(manifest, defaultNs, st.logger)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse release resources from manifest: %w", err)
|
|
}
|
|
|
|
if len(resources) == 0 {
|
|
st.logger.Infof("No resources found in manifest for release %s", release.Name)
|
|
return nil, nil
|
|
}
|
|
|
|
st.logger.Infof("Found %d resources in manifest for release %s", len(resources), release.Name)
|
|
|
|
result := make([]*resource.Resource, len(resources))
|
|
for i := range resources {
|
|
result[i] = &resources[i]
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (st *HelmState) getReleaseManifest(release *ReleaseSpec, helm helmexec.Interface) ([]byte, string, error) {
|
|
var tempDir string
|
|
var err error
|
|
|
|
if st.tempDir != nil {
|
|
tempDir, err = st.tempDir("", "helmfile-template-")
|
|
} else {
|
|
tempDir, err = os.MkdirTemp("", "helmfile-template-")
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("failed to create temp directory: %w", err)
|
|
}
|
|
defer func() {
|
|
if err := os.RemoveAll(tempDir); err != nil {
|
|
st.logger.Warnf("Failed to remove temp directory %s: %v", tempDir, err)
|
|
}
|
|
}()
|
|
|
|
releaseCopy := *release
|
|
st.ApplyOverrides(&releaseCopy)
|
|
|
|
flags, files, err := st.flagsForTemplate(helm, &releaseCopy, 0, &TemplateOpts{})
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("failed to generate template flags: %w", err)
|
|
}
|
|
defer st.removeFiles(files)
|
|
|
|
flags = append(flags, "--output-dir", tempDir)
|
|
|
|
if err := helm.TemplateRelease(releaseCopy.Name, releaseCopy.ChartPathOrName(), flags...); err != nil {
|
|
return nil, "", fmt.Errorf("failed to run helm template: %w", err)
|
|
}
|
|
|
|
var manifest []byte
|
|
|
|
err = filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
if !strings.HasSuffix(info.Name(), ".yaml") && !strings.HasSuffix(info.Name(), ".yml") {
|
|
return nil
|
|
}
|
|
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read file %s: %w", path, err)
|
|
}
|
|
|
|
if len(manifest) > 0 {
|
|
manifest = append(manifest, []byte("\n---\n")...)
|
|
}
|
|
manifest = append(manifest, content...)
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("failed to walk template output directory: %w", err)
|
|
}
|
|
|
|
return manifest, releaseCopy.Namespace, nil
|
|
}
|
|
|
|
func convertTrackResources(resources []TrackResourceSpec) []resource.Resource {
|
|
if len(resources) == 0 {
|
|
return nil
|
|
}
|
|
result := make([]resource.Resource, len(resources))
|
|
for i, r := range resources {
|
|
result[i] = resource.Resource{
|
|
Kind: r.Kind,
|
|
Name: r.Name,
|
|
Namespace: r.Namespace,
|
|
}
|
|
}
|
|
return result
|
|
}
|