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 }