github.com/helmwave/helmwave@v0.36.4-0.20240509190856-b35563eba4c6/pkg/plan/export.go (about)

     1  package plan
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"slices"
    10  
    11  	"github.com/helmwave/helmwave/pkg/helper"
    12  	"github.com/helmwave/helmwave/pkg/parallel"
    13  	"github.com/helmwave/helmwave/pkg/release"
    14  )
    15  
    16  // Export allows save plan to file.
    17  func (p *Plan) Export(ctx context.Context, skipUnchanged bool) error {
    18  	if err := os.RemoveAll(p.dir); err != nil {
    19  		return fmt.Errorf("failed to clean plan directory %s: %w", p.dir, err)
    20  	}
    21  	defer func(dir string) {
    22  		err := os.RemoveAll(dir)
    23  		if err != nil {
    24  			p.Logger().WithError(err).Error("failed to remove temporary directory")
    25  		}
    26  	}(p.tmpDir)
    27  
    28  	if skipUnchanged {
    29  		p.removeUnchanged()
    30  		p.Logger().Info("removed unchanged releases from plan")
    31  	}
    32  
    33  	wg := parallel.NewWaitGroup()
    34  	wg.Add(4)
    35  
    36  	go func() {
    37  		defer wg.Done()
    38  		if err := p.exportCharts(); err != nil {
    39  			wg.ErrChan() <- err
    40  		}
    41  	}()
    42  	go func() {
    43  		defer wg.Done()
    44  		if err := p.exportManifest(); err != nil {
    45  			wg.ErrChan() <- err
    46  		}
    47  	}()
    48  	go func() {
    49  		defer wg.Done()
    50  		if err := p.exportValues(); err != nil {
    51  			wg.ErrChan() <- err
    52  		}
    53  	}()
    54  	go func() {
    55  		defer wg.Done()
    56  		if err := p.exportGraphMD(); err != nil {
    57  			wg.ErrChan() <- err
    58  		}
    59  	}()
    60  
    61  	err := wg.Wait()
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	// Save Planfile after everything is exported
    67  	return helper.SaveInterface(ctx, p.fullPath, p.body)
    68  }
    69  
    70  func (p *Plan) removeUnchanged() {
    71  	p.body.Releases = slices.DeleteFunc(p.body.Releases, func(rel release.Config) bool {
    72  		return slices.ContainsFunc(p.unchanged, func(r release.Config) bool {
    73  			return r.Uniq().Equal(rel.Uniq())
    74  		})
    75  	})
    76  }
    77  
    78  func (p *Plan) exportCharts() error {
    79  	for i, rel := range p.body.Releases {
    80  		l := p.Logger().WithField("release", rel.Uniq())
    81  
    82  		if !rel.Chart().IsRemote() {
    83  			l.Info("chart is local, skipping exporting it")
    84  
    85  			continue
    86  		}
    87  
    88  		src := path.Join(p.tmpDir, "charts", rel.Uniq().String())
    89  		dst := path.Join(p.dir, "charts", rel.Uniq().String())
    90  		err := helper.MoveFile(
    91  			src,
    92  			dst,
    93  		)
    94  		if err != nil {
    95  			return err
    96  		}
    97  
    98  		// Chart is places as an archive under this directory.
    99  		// So we need to find it and use.
   100  		entries, err := os.ReadDir(dst)
   101  		if err != nil {
   102  			l.WithError(err).Warn("failed to read directory with downloaded chart, skipping")
   103  
   104  			continue
   105  		}
   106  
   107  		if len(entries) != 1 {
   108  			l.WithField("entries", entries).Warn("don't know which file is downloaded chart, skipping")
   109  
   110  			continue
   111  		}
   112  
   113  		chart := entries[0]
   114  		p.body.Releases[i].SetChartName(path.Join(dst, chart.Name()))
   115  	}
   116  
   117  	return nil
   118  }
   119  
   120  func (p *Plan) exportManifest() error {
   121  	for k, v := range p.manifests {
   122  		m := filepath.Join(p.dir, Manifest, k.String()+".yml")
   123  
   124  		f, err := helper.CreateFile(m)
   125  		if err != nil {
   126  			return err
   127  		}
   128  
   129  		_, err = f.WriteString(v)
   130  		if err != nil {
   131  			return fmt.Errorf("failed to write manifest %s: %w", f.Name(), err)
   132  		}
   133  
   134  		err = f.Close()
   135  		if err != nil {
   136  			return fmt.Errorf("failed to close manifest %s: %w", f.Name(), err)
   137  		}
   138  	}
   139  
   140  	return nil
   141  }
   142  
   143  func (p *Plan) exportGraphMD() error {
   144  	found := slices.ContainsFunc(p.body.Releases, func(rel release.Config) bool {
   145  		return len(rel.DependsOn()) > 0
   146  	})
   147  	if !found {
   148  		return nil
   149  	}
   150  
   151  	const filename = "graph.md"
   152  	f, err := helper.CreateFile(filepath.Join(p.dir, filename))
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	_, err = f.WriteString(p.graphMD)
   158  	if err != nil {
   159  		return fmt.Errorf("failed to write graph file %s: %w", f.Name(), err)
   160  	}
   161  
   162  	if err := f.Close(); err != nil {
   163  		return fmt.Errorf("failed to close graph file %s: %w", f.Name(), err)
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  func (p *Plan) exportValues() error {
   170  	found := false
   171  
   172  	for i, rel := range p.body.Releases {
   173  		for j := range p.body.Releases[i].Values() {
   174  			found = true
   175  			p.body.Releases[i].Values()[j].SetUniq(p.dir, rel.Uniq())
   176  		}
   177  	}
   178  
   179  	if !found {
   180  		return nil
   181  	}
   182  
   183  	// It doesnt work if workdir has been mounted.
   184  	err := helper.MoveFile(
   185  		filepath.Join(p.tmpDir, Values),
   186  		filepath.Join(p.dir, Values),
   187  	)
   188  	if err != nil {
   189  		return fmt.Errorf("failed to copy values from %s to %s: %w", p.tmpDir, p.dir, err)
   190  	}
   191  
   192  	return nil
   193  }
   194  
   195  // IsExist returns true if planfile exists.
   196  func (p *Plan) IsExist() bool {
   197  	return helper.IsExists(p.fullPath)
   198  }
   199  
   200  // IsManifestExist returns true if planfile exists.
   201  func (p *Plan) IsManifestExist() bool {
   202  	return helper.IsExists(filepath.Join(p.dir, Manifest))
   203  }