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 }