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  }