github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/layout/component.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2021-Present The Jackal Authors 3 4 // Package layout contains functions for interacting with Jackal's package layout on disk. 5 package layout 6 7 import ( 8 "fmt" 9 "io/fs" 10 "os" 11 "path/filepath" 12 13 "github.com/Racer159/jackal/src/pkg/message" 14 "github.com/Racer159/jackal/src/types" 15 "github.com/defenseunicorns/pkg/helpers" 16 "github.com/mholt/archiver/v3" 17 ) 18 19 // ComponentPaths contains paths for a component. 20 type ComponentPaths struct { 21 Base string 22 Temp string 23 Files string 24 Charts string 25 Values string 26 Repos string 27 Manifests string 28 DataInjections string 29 } 30 31 // Components contains paths for components. 32 type Components struct { 33 Base string 34 Dirs map[string]*ComponentPaths 35 Tarballs map[string]string 36 } 37 38 // ErrNotLoaded is returned when a path is not loaded. 39 var ErrNotLoaded = fmt.Errorf("not loaded") 40 41 // IsNotLoaded checks if an error is ErrNotLoaded. 42 func IsNotLoaded(err error) bool { 43 u, ok := err.(*fs.PathError) 44 return ok && u.Unwrap() == ErrNotLoaded 45 } 46 47 // Archive archives a component. 48 func (c *Components) Archive(component types.JackalComponent, cleanupTemp bool) (err error) { 49 name := component.Name 50 if _, ok := c.Dirs[name]; !ok { 51 return &fs.PathError{ 52 Op: "check dir map for", 53 Path: name, 54 Err: ErrNotLoaded, 55 } 56 } 57 base := c.Dirs[name].Base 58 if cleanupTemp { 59 _ = os.RemoveAll(c.Dirs[name].Temp) 60 } 61 size, err := helpers.GetDirSize(base) 62 if err != nil { 63 return err 64 } 65 if size > 0 { 66 tb := fmt.Sprintf("%s.tar", base) 67 message.Debugf("Archiving %q", name) 68 if err := helpers.CreateReproducibleTarballFromDir(base, name, tb); err != nil { 69 return err 70 } 71 if c.Tarballs == nil { 72 c.Tarballs = make(map[string]string) 73 } 74 c.Tarballs[name] = tb 75 } else { 76 message.Debugf("Component %q is empty, skipping archiving", name) 77 } 78 79 delete(c.Dirs, name) 80 return os.RemoveAll(base) 81 } 82 83 // Unarchive unarchives a component. 84 func (c *Components) Unarchive(component types.JackalComponent) (err error) { 85 name := component.Name 86 tb, ok := c.Tarballs[name] 87 if !ok { 88 return &fs.PathError{ 89 Op: "check tarball map for", 90 Path: name, 91 Err: ErrNotLoaded, 92 } 93 } 94 95 if helpers.InvalidPath(tb) { 96 return &fs.PathError{ 97 Op: "stat", 98 Path: tb, 99 Err: fs.ErrNotExist, 100 } 101 } 102 103 cs := &ComponentPaths{ 104 Base: filepath.Join(c.Base, name), 105 } 106 if len(component.Files) > 0 { 107 cs.Files = filepath.Join(cs.Base, FilesDir) 108 } 109 if len(component.Charts) > 0 { 110 cs.Charts = filepath.Join(cs.Base, ChartsDir) 111 for _, chart := range component.Charts { 112 if len(chart.ValuesFiles) > 0 { 113 cs.Values = filepath.Join(cs.Base, ValuesDir) 114 break 115 } 116 } 117 } 118 if len(component.Repos) > 0 { 119 cs.Repos = filepath.Join(cs.Base, ReposDir) 120 } 121 if len(component.Manifests) > 0 { 122 cs.Manifests = filepath.Join(cs.Base, ManifestsDir) 123 } 124 if len(component.DataInjections) > 0 { 125 cs.DataInjections = filepath.Join(cs.Base, DataInjectionsDir) 126 } 127 if c.Dirs == nil { 128 c.Dirs = make(map[string]*ComponentPaths) 129 } 130 c.Dirs[name] = cs 131 delete(c.Tarballs, name) 132 133 // if the component is already unarchived, skip 134 if !helpers.InvalidPath(cs.Base) { 135 message.Debugf("Component %q already unarchived", name) 136 return nil 137 } 138 139 message.Debugf("Unarchiving %q", filepath.Base(tb)) 140 if err := archiver.Unarchive(tb, c.Base); err != nil { 141 return err 142 } 143 return os.Remove(tb) 144 } 145 146 // Create creates a new component directory structure. 147 func (c *Components) Create(component types.JackalComponent) (cp *ComponentPaths, err error) { 148 name := component.Name 149 150 _, ok := c.Tarballs[name] 151 if ok { 152 return nil, &fs.PathError{ 153 Op: "create component paths", 154 Path: name, 155 Err: fmt.Errorf("component tarball for %q exists, use Unarchive instead", name), 156 } 157 } 158 159 if err = helpers.CreateDirectory(c.Base, helpers.ReadWriteExecuteUser); err != nil { 160 return nil, err 161 } 162 163 base := filepath.Join(c.Base, name) 164 165 if err = helpers.CreateDirectory(base, helpers.ReadWriteExecuteUser); err != nil { 166 return nil, err 167 } 168 169 cp = &ComponentPaths{ 170 Base: base, 171 } 172 173 cp.Temp = filepath.Join(base, TempDir) 174 if err = helpers.CreateDirectory(cp.Temp, helpers.ReadWriteExecuteUser); err != nil { 175 return nil, err 176 } 177 178 if len(component.Files) > 0 { 179 cp.Files = filepath.Join(base, FilesDir) 180 if err = helpers.CreateDirectory(cp.Files, helpers.ReadWriteExecuteUser); err != nil { 181 return nil, err 182 } 183 } 184 185 if len(component.Charts) > 0 { 186 cp.Charts = filepath.Join(base, ChartsDir) 187 if err = helpers.CreateDirectory(cp.Charts, helpers.ReadWriteExecuteUser); err != nil { 188 return nil, err 189 } 190 for _, chart := range component.Charts { 191 cp.Values = filepath.Join(base, ValuesDir) 192 if len(chart.ValuesFiles) > 0 { 193 if err = helpers.CreateDirectory(cp.Values, helpers.ReadWriteExecuteUser); err != nil { 194 return nil, err 195 } 196 break 197 } 198 } 199 } 200 201 if len(component.Repos) > 0 { 202 cp.Repos = filepath.Join(base, ReposDir) 203 if err = helpers.CreateDirectory(cp.Repos, helpers.ReadWriteExecuteUser); err != nil { 204 return nil, err 205 } 206 } 207 208 if len(component.Manifests) > 0 { 209 cp.Manifests = filepath.Join(base, ManifestsDir) 210 if err = helpers.CreateDirectory(cp.Manifests, helpers.ReadWriteExecuteUser); err != nil { 211 return nil, err 212 } 213 } 214 215 if len(component.DataInjections) > 0 { 216 cp.DataInjections = filepath.Join(base, DataInjectionsDir) 217 if err = helpers.CreateDirectory(cp.DataInjections, helpers.ReadWriteExecuteUser); err != nil { 218 return nil, err 219 } 220 } 221 222 if c.Dirs == nil { 223 c.Dirs = make(map[string]*ComponentPaths) 224 } 225 226 c.Dirs[name] = cp 227 return cp, nil 228 }