feat: implement part of metasnap api
This commit is contained in:
		
							
								
								
									
										158
									
								
								distro/debian/snapshot/metasnap/metasnap.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								distro/debian/snapshot/metasnap/metasnap.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,158 @@
 | 
				
			|||||||
 | 
					package metasnap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
 | 
						"golang.org/x/time/rate"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const apiURL = "http://metasnap.debian.net/cgi-bin/api?"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var archives = []string{
 | 
				
			||||||
 | 
						"debian",
 | 
				
			||||||
 | 
						"debian-security",
 | 
				
			||||||
 | 
						"debian-backports",
 | 
				
			||||||
 | 
						// "debian-archive",
 | 
				
			||||||
 | 
						// "debian-debug",
 | 
				
			||||||
 | 
						// "debian-ports",
 | 
				
			||||||
 | 
						// "debian-volatile",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						limiterTimeout time.Duration = time.Second
 | 
				
			||||||
 | 
						limiterBurst   int           = 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Limiter = rate.NewLimiter(rate.Every(limiterTimeout), limiterBurst)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lowerLimit() {
 | 
				
			||||||
 | 
						limiterTimeout = limiterTimeout * 2
 | 
				
			||||||
 | 
						log.Info().Msgf("limiter timeout set to %v", limiterTimeout)
 | 
				
			||||||
 | 
						Limiter.SetLimit(rate.Every(limiterTimeout))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Retries in case of 5xx errors
 | 
				
			||||||
 | 
					var Retries = 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var ErrNotFound = errors.New("404 not found")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func query(q string) (result string, err error) {
 | 
				
			||||||
 | 
						flog := log.With().Str("url", q).Logger()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var resp *http.Response
 | 
				
			||||||
 | 
						for i := Retries; i > 0; i-- {
 | 
				
			||||||
 | 
							flog.Trace().Msg("wait")
 | 
				
			||||||
 | 
							Limiter.Wait(context.Background())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							flog.Trace().Msg("start")
 | 
				
			||||||
 | 
							resp, err = http.Get(q)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if strings.Contains(err.Error(), "reset by peer") {
 | 
				
			||||||
 | 
									flog.Debug().Err(err).Msg("")
 | 
				
			||||||
 | 
									lowerLimit()
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								flog.Error().Err(err).Msg("")
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							flog.Debug().Msgf("%s", resp.Status)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if resp.StatusCode == 404 {
 | 
				
			||||||
 | 
								err = ErrNotFound
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if resp.StatusCode < 500 {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							flog.Debug().Msgf("retry (%d left)", i)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if resp.StatusCode >= 400 {
 | 
				
			||||||
 | 
							err = fmt.Errorf("%d (%s)", resp.StatusCode, q)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						buf, err := io.ReadAll(resp.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result = string(buf)
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func queryAPIf(f string, s ...interface{}) (result string, err error) {
 | 
				
			||||||
 | 
						return query(apiURL + fmt.Sprintf(f, s...))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Snapshot struct {
 | 
				
			||||||
 | 
						First string
 | 
				
			||||||
 | 
						Last  string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PackageInfo struct {
 | 
				
			||||||
 | 
						Suite     string
 | 
				
			||||||
 | 
						Component string
 | 
				
			||||||
 | 
						Snapshot  Snapshot
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetPackageInfo(pkg, arch, version string) (infos []PackageInfo, err error) {
 | 
				
			||||||
 | 
						var result string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, archive := range archives {
 | 
				
			||||||
 | 
							result, err = queryAPIf("archive=%s&pkg=%s&arch=%s&ver=%s",
 | 
				
			||||||
 | 
								archive, pkg, arch, version)
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if result == "" {
 | 
				
			||||||
 | 
							err = ErrNotFound
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, line := range strings.Split(result, "\n") {
 | 
				
			||||||
 | 
							if line == "" {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fields := strings.Split(line, " ")
 | 
				
			||||||
 | 
							if len(fields) != 4 {
 | 
				
			||||||
 | 
								err = fmt.Errorf("metasnap api returned %s", result)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							info := PackageInfo{
 | 
				
			||||||
 | 
								Suite:     fields[0],
 | 
				
			||||||
 | 
								Component: fields[1],
 | 
				
			||||||
 | 
								Snapshot: Snapshot{
 | 
				
			||||||
 | 
									First: fields[2],
 | 
				
			||||||
 | 
									Last:  fields[3],
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							infos = append(infos, info)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(infos) == 0 {
 | 
				
			||||||
 | 
							err = ErrNotFound
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								distro/debian/snapshot/metasnap/metasnap_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								distro/debian/snapshot/metasnap/metasnap_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					package metasnap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/davecgh/go-spew/spew"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGetPackageInfo(t *testing.T) {
 | 
				
			||||||
 | 
						// existing
 | 
				
			||||||
 | 
						infos, err := GetPackageInfo("linux-image-3.8-trunk-amd64",
 | 
				
			||||||
 | 
							"amd64", "3.8.2-1~experimental.1")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Log(spew.Sdump(infos))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// non-existing
 | 
				
			||||||
 | 
						infos, err = GetPackageInfo("meh", "amd64", "meh")
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							t.Fatalf("should not be ok, result: %s", spew.Sdump(infos))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != ErrNotFound {
 | 
				
			||||||
 | 
							t.Fatal("wrong error type")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user