github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/cache/cache_opts.go (about)

     1  package cache
     2  
     3  import (
     4  	"encoding/csv"
     5  	"fmt"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/pkg/errors"
    10  )
    11  
    12  type Format int
    13  type CacheInfo struct {
    14  	Format Format
    15  	Source string
    16  }
    17  
    18  type CacheOpts struct {
    19  	Build  CacheInfo
    20  	Launch CacheInfo
    21  	Kaniko CacheInfo
    22  }
    23  
    24  const (
    25  	CacheVolume Format = iota
    26  	CacheImage
    27  	CacheBind
    28  )
    29  
    30  func (f Format) String() string {
    31  	switch f {
    32  	case CacheImage:
    33  		return "image"
    34  	case CacheVolume:
    35  		return "volume"
    36  	case CacheBind:
    37  		return "bind"
    38  	}
    39  	return ""
    40  }
    41  
    42  func (c *CacheInfo) SourceName() string {
    43  	switch c.Format {
    44  	case CacheImage:
    45  		fallthrough
    46  	case CacheVolume:
    47  		return "name"
    48  	case CacheBind:
    49  		return "source"
    50  	}
    51  	return ""
    52  }
    53  
    54  func (c *CacheOpts) Set(value string) error {
    55  	csvReader := csv.NewReader(strings.NewReader(value))
    56  	csvReader.Comma = ';'
    57  	fields, err := csvReader.Read()
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	cache := &c.Build
    63  	for _, field := range fields {
    64  		parts := strings.SplitN(field, "=", 2)
    65  		if len(parts) != 2 {
    66  			return errors.Errorf("invalid field '%s' must be a key=value pair", field)
    67  		}
    68  		key := strings.ToLower(parts[0])
    69  		value := strings.ToLower(parts[1])
    70  		if key == "type" {
    71  			switch value {
    72  			case "build":
    73  				cache = &c.Build
    74  			case "launch":
    75  				cache = &c.Launch
    76  			default:
    77  				return errors.Errorf("invalid cache type '%s'", value)
    78  			}
    79  			break
    80  		}
    81  	}
    82  
    83  	for _, field := range fields {
    84  		parts := strings.SplitN(field, "=", 2)
    85  		if len(parts) != 2 {
    86  			return errors.Errorf("invalid field '%s' must be a key=value pair", field)
    87  		}
    88  		key := strings.ToLower(parts[0])
    89  		value := strings.ToLower(parts[1])
    90  		switch key {
    91  		case "format":
    92  			switch value {
    93  			case "image":
    94  				cache.Format = CacheImage
    95  			case "volume":
    96  				cache.Format = CacheVolume
    97  			case "bind":
    98  				cache.Format = CacheBind
    99  			default:
   100  				return errors.Errorf("invalid cache format '%s'", value)
   101  			}
   102  		case "name":
   103  			cache.Source = value
   104  		case "source":
   105  			cache.Source = value
   106  		}
   107  	}
   108  
   109  	err = sanitize(c)
   110  	if err != nil {
   111  		return err
   112  	}
   113  	return nil
   114  }
   115  
   116  func (c *CacheOpts) String() string {
   117  	var cacheFlag string
   118  	cacheFlag = fmt.Sprintf("type=build;format=%s;", c.Build.Format.String())
   119  	if c.Build.Source != "" {
   120  		cacheFlag += fmt.Sprintf("%s=%s;", c.Build.SourceName(), c.Build.Source)
   121  	}
   122  
   123  	cacheFlag += fmt.Sprintf("type=launch;format=%s;", c.Launch.Format.String())
   124  	if c.Launch.Source != "" {
   125  		cacheFlag += fmt.Sprintf("%s=%s;", c.Launch.SourceName(), c.Launch.Source)
   126  	}
   127  
   128  	return cacheFlag
   129  }
   130  
   131  func (c *CacheOpts) Type() string {
   132  	return "cache"
   133  }
   134  
   135  func sanitize(c *CacheOpts) error {
   136  	for _, v := range []CacheInfo{c.Build, c.Launch} {
   137  		// volume cache name can be auto-generated
   138  		if v.Format != CacheVolume && v.Source == "" {
   139  			return errors.Errorf("cache '%s' is required", v.SourceName())
   140  		}
   141  	}
   142  
   143  	var (
   144  		resolvedPath string
   145  		err          error
   146  	)
   147  	if c.Build.Format == CacheBind {
   148  		if resolvedPath, err = filepath.Abs(c.Build.Source); err != nil {
   149  			return errors.Wrap(err, "resolve absolute path")
   150  		}
   151  		c.Build.Source = filepath.Join(resolvedPath, "build-cache")
   152  	}
   153  	if c.Launch.Format == CacheBind {
   154  		if resolvedPath, err = filepath.Abs(c.Launch.Source); err != nil {
   155  			return errors.Wrap(err, "resolve absolute path")
   156  		}
   157  		c.Launch.Source = filepath.Join(resolvedPath, "launch-cache")
   158  	}
   159  	return nil
   160  }