github.com/sirkon/goproxy@v1.4.8/plugin/aposteriori/module.go (about)

     1  package aposteriori
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"io"
     8  	"io/ioutil"
     9  	"path"
    10  	"sort"
    11  	"strings"
    12  
    13  	"github.com/rs/zerolog"
    14  	"github.com/sirkon/goproxy/internal/errors"
    15  
    16  	"github.com/sirkon/goproxy"
    17  	"github.com/sirkon/goproxy/semver"
    18  )
    19  
    20  type module struct {
    21  	parent *plugin
    22  	next   goproxy.Module
    23  }
    24  
    25  func (m *module) ModulePath() string {
    26  	return m.next.ModulePath()
    27  }
    28  
    29  func (m *module) Versions(ctx context.Context, prefix string) (tags []string, err error) {
    30  	if m.parent.registry != nil {
    31  		m.parent.Lock()
    32  		defer m.parent.Unlock()
    33  		versions, ok := m.parent.registry[m.ModulePath()]
    34  		if ok {
    35  			zerolog.Ctx(ctx).Info().Msg("module versions list detected in a cache")
    36  			for version := range versions {
    37  				if strings.HasPrefix(version, prefix) {
    38  					tags = append(tags, version)
    39  				}
    40  			}
    41  			sort.Slice(tags, func(i, j int) bool {
    42  				return semver.Compare(tags[i], tags[j]) < 0
    43  			})
    44  		}
    45  		return tags, nil
    46  	}
    47  	return m.next.Versions(ctx, prefix)
    48  }
    49  
    50  func (m *module) Stat(ctx context.Context, rev string) (*goproxy.RevInfo, error) {
    51  	if semver.IsValid(rev) {
    52  		p := m.relPath(rev, "revinfo.json")
    53  		res, err := m.parent.cache.Get(p)
    54  		if err == nil {
    55  			zerolog.Ctx(ctx).Info().Msg("module revision info for given version detected in a cache")
    56  			defer func() {
    57  				if err := res.Close(); err != nil {
    58  					zerolog.Ctx(ctx).Error().Err(err).Msg("failed to close rev.info")
    59  				}
    60  			}()
    61  			var dst goproxy.RevInfo
    62  			unmr := json.NewDecoder(res)
    63  			if err := unmr.Decode(&dst); err != nil {
    64  				return nil, errors.Wrapf(err, "getting revision info: %s")
    65  			}
    66  			return &dst, nil
    67  		}
    68  		zerolog.Ctx(ctx).Error().Err(err).Msg("revision info not found in cache")
    69  	}
    70  	res, err := m.next.Stat(ctx, rev)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	var dst bytes.Buffer
    75  	mrsr := json.NewEncoder(&dst)
    76  	if err := mrsr.Encode(res); err != nil {
    77  		return nil, errors.Wrapf(err, "marshaling revision info")
    78  	}
    79  	p := m.relPath(res.Version, "revinfo.json")
    80  	if err := m.parent.cache.Set(p, &dst); err != nil {
    81  		zerolog.Ctx(ctx).Error().Err(err).Msg("aposteriori failed to cache revision info")
    82  	}
    83  	return res, nil
    84  }
    85  
    86  func (m *module) relPath(version, name string) string {
    87  	return path.Join(m.next.ModulePath(), version, name)
    88  }
    89  
    90  func (m *module) GoMod(ctx context.Context, version string) (data []byte, err error) {
    91  	p := m.relPath(version, "go.mod")
    92  	res, err := m.parent.cache.Get(p)
    93  	if err == nil {
    94  		zerolog.Ctx(ctx).Info().Msg("module go.mod for given version detected in a cache")
    95  		defer func() {
    96  			if err := res.Close(); err != nil {
    97  				zerolog.Ctx(ctx).Error().Err(err).Msg("failed to close response cache")
    98  			}
    99  		}()
   100  		return ioutil.ReadAll(res)
   101  	}
   102  	zerolog.Ctx(ctx).Debug().Err(err).Msg("no cached go.mod found")
   103  
   104  	data, err = m.next.GoMod(ctx, version)
   105  	if err != nil {
   106  		return nil, errors.Wrapf(err, "aposteriori go.mod delegation")
   107  	}
   108  
   109  	if err := m.parent.cache.Set(p, bytes.NewReader(data)); err != nil {
   110  		zerolog.Ctx(ctx).Error().Err(err).Msg("failed to cache go.mod")
   111  	}
   112  	return data, nil
   113  }
   114  
   115  var _ io.ReadCloser = &cachingReadCloser{}
   116  
   117  type cachingReadCloser struct {
   118  	logger *zerolog.Logger
   119  	src    io.ReadCloser
   120  	buf    *bytes.Buffer
   121  
   122  	plugin  *plugin
   123  	module  *module
   124  	version string
   125  
   126  	name       string
   127  	doNotCache bool
   128  }
   129  
   130  func (r *cachingReadCloser) Read(p []byte) (n int, err error) {
   131  	n, err = r.src.Read(p)
   132  	if n > 0 && !r.doNotCache {
   133  		if _, cErr := r.buf.Write(p[:n]); cErr != nil {
   134  			r.logger.Warn().Err(cErr).Msg("aposteriori: failed to copy written data into underlying buffer")
   135  			r.doNotCache = true
   136  		}
   137  	}
   138  	return n, err
   139  }
   140  
   141  func (r *cachingReadCloser) Close() error {
   142  	err := r.src.Close()
   143  	if !r.doNotCache {
   144  		if sErr := r.plugin.cache.Set(r.name, r.buf); sErr != nil {
   145  			r.logger.Error().Err(err).Msg("aposteriori: failed to save incoming source archive into cache")
   146  			return nil
   147  		}
   148  		if r.plugin.registry != nil {
   149  			r.plugin.Lock()
   150  			res, ok := r.plugin.registry[r.module.ModulePath()]
   151  			if !ok {
   152  				res = map[string]struct{}{}
   153  			}
   154  			res[r.version] = struct{}{}
   155  			r.plugin.registry[r.module.ModulePath()] = res
   156  			r.plugin.Unlock()
   157  		}
   158  	}
   159  	return err
   160  }
   161  
   162  func (m *module) Zip(ctx context.Context, version string) (io.ReadCloser, error) {
   163  	p := m.relPath(version, "src.zip")
   164  	file, err := m.parent.cache.Get(p)
   165  	if err == nil {
   166  		zerolog.Ctx(ctx).Info().Msg("module source archive for given version detected in a cache")
   167  		return file, err
   168  	}
   169  	zerolog.Ctx(ctx).Debug().Err(err).Msg("no cached module source found")
   170  
   171  	file, err = m.next.Zip(ctx, version)
   172  	if err != nil {
   173  		return nil, errors.Wrapf(err, "aposteriori source delegation")
   174  	}
   175  
   176  	file = &cachingReadCloser{
   177  		logger:  zerolog.Ctx(ctx),
   178  		src:     file,
   179  		buf:     &bytes.Buffer{},
   180  		plugin:  m.parent,
   181  		module:  m,
   182  		version: version,
   183  		name:    p,
   184  	}
   185  	return file, nil
   186  }