github.com/criteo/command-launcher@v0.0.0-20230407142452-fb616f546e98/internal/remote/default-remote.go (about) 1 package remote 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "path/filepath" 8 "sort" 9 "strings" 10 11 "github.com/criteo/command-launcher/internal/command" 12 "github.com/criteo/command-launcher/internal/helper" 13 "github.com/criteo/command-launcher/internal/pkg" 14 log "github.com/sirupsen/logrus" 15 ) 16 17 var ( 18 ErrMsg_PackageNotFound = "package not found" 19 ) 20 21 func IsPackageNotFound(err error) bool { 22 return strings.HasPrefix(err.Error(), ErrMsg_PackageNotFound) 23 } 24 25 type defaultRemoteRepository struct { 26 repoBaseUrl string 27 PackagesByName map[string]PackagesByVersion 28 } 29 30 func newRemoteRepository(baseUrl string) *defaultRemoteRepository { 31 return &defaultRemoteRepository{ 32 repoBaseUrl: baseUrl, 33 PackagesByName: make(map[string]PackagesByVersion), 34 } 35 } 36 37 func (remote *defaultRemoteRepository) Fetch() error { 38 return remote.load() 39 } 40 41 func (remote *defaultRemoteRepository) All() ([]PackageInfo, error) { 42 packages := make([]PackageInfo, 0) 43 if err := remote.load(); err != nil { 44 return packages, err 45 } 46 for _, list := range remote.PackagesByName { 47 packages = append(packages, list...) 48 } 49 return packages, nil 50 } 51 52 func (remote *defaultRemoteRepository) PackageNames() ([]string, error) { 53 packages := make([]string, 0) 54 if err := remote.load(); err != nil { 55 return packages, err 56 } 57 for key := range remote.PackagesByName { 58 packages = append(packages, key) 59 } 60 return packages, nil 61 } 62 63 func (remote *defaultRemoteRepository) Versions(pkgName string) ([]string, error) { 64 results := make([]string, 0) 65 if err := remote.load(); err != nil { 66 return results, err 67 } 68 pkgInfos, exists := remote.PackagesByName[pkgName] 69 if exists { 70 for _, info := range pkgInfos { 71 var version defaultVersion 72 err := ParseVersion(info.Version, &version) 73 if err == nil && info.Name == pkgName { 74 results = append(results, version.String()) 75 } 76 } 77 } 78 79 return results, nil 80 } 81 82 func (remote *defaultRemoteRepository) PackageInfosByCmdName(pkgName string) ([]PackageInfo, error) { 83 if err := remote.load(); err != nil { 84 return []PackageInfo{}, err 85 } 86 pkgInfos, exists := remote.PackagesByName[pkgName] 87 if exists { 88 return pkgInfos, nil 89 } 90 return make(PackagesByVersion, 0), nil 91 } 92 93 func (remote *defaultRemoteRepository) LatestVersion(pkgName string) (string, error) { 94 return remote.QueryLatestVersion(pkgName, func(pkgInfo *PackageInfo) bool { 95 return true 96 }) 97 } 98 99 func (remote *defaultRemoteRepository) QueryLatestVersion(pkgName string, filter PackageInfoFilterFunc) (string, error) { 100 pkgInfo, err := remote.QueryLatestPackageInfo(pkgName, filter) 101 if err != nil { 102 return "", err 103 } 104 if pkgInfo == nil { 105 return "", fmt.Errorf("%s: %s", ErrMsg_PackageNotFound, pkgName) 106 } 107 return pkgInfo.Version, nil 108 } 109 110 func (remote *defaultRemoteRepository) LatestPackageInfo(pkgName string) (*PackageInfo, error) { 111 return remote.QueryLatestPackageInfo(pkgName, func(pkgInfo *PackageInfo) bool { 112 return true 113 }) 114 } 115 116 func (remote *defaultRemoteRepository) QueryLatestPackageInfo(pkgName string, filter PackageInfoFilterFunc) (*PackageInfo, error) { 117 pkgInfos, err := remote.PackageInfosByCmdName(pkgName) 118 if err != nil { 119 return nil, err 120 } 121 if len(pkgInfos) == 0 { 122 return nil, fmt.Errorf("%s in remote repository: %s", ErrMsg_PackageNotFound, pkgName) 123 } 124 for i := len(pkgInfos) - 1; i >= 0; i-- { 125 if filter(&pkgInfos[i]) { 126 return &pkgInfos[i], nil 127 } 128 } 129 return nil, fmt.Errorf("%s in remote repository: %s. The package exists, but no version match the query filter", ErrMsg_PackageNotFound, pkgName) 130 } 131 132 // get package from remote registry, this will download the package 133 func (remote *defaultRemoteRepository) Package(pkgName string, pkgVersion string) (command.Package, error) { 134 tmpDir, err := os.MkdirTemp("", "package-download-*") 135 if err != nil { 136 return nil, fmt.Errorf("cannot create temporary dir (%v)", err) 137 } 138 139 pkgPathname := filepath.Join(tmpDir, fmt.Sprintf("%s-%s.pkg", pkgName, pkgVersion)) 140 141 url := remote.url(pkgName, pkgVersion) 142 143 if err := helper.DownloadFile(url, pkgPathname, true); err != nil { 144 return nil, fmt.Errorf("error downloading %s: %v", url, err) 145 } 146 147 pkg, err := pkg.CreateZipPackage(pkgPathname) 148 if err != nil { 149 return nil, fmt.Errorf("invalid package %s: %v", url, err) 150 } 151 152 return pkg, nil 153 } 154 155 func (remote *defaultRemoteRepository) PackageInfo(pkgName string, pkgVersion string) (*PackageInfo, error) { 156 return remote.findPackage(pkgName, pkgVersion) 157 } 158 159 func (remote *defaultRemoteRepository) Verify(pkg command.Package, verifyChecksum, verifySignature bool) (bool, error) { 160 pkgName := pkg.Name() 161 pkgVersion := pkg.Version() 162 pkgInfo, err := remote.PackageInfo(pkgName, pkgVersion) 163 if err != nil { 164 return false, fmt.Errorf("Failed to get package information %s: %v\n", pkgName, err) 165 } 166 if verifyChecksum { 167 if ok, err := pkg.VerifyChecksum(pkgInfo.Checksum); !ok || err != nil { 168 return false, err 169 } 170 } 171 if verifySignature { 172 // TODO: add signature verification here 173 return true, nil 174 } 175 return true, nil 176 } 177 178 func (remote *defaultRemoteRepository) findPackage(name string, version string) (*PackageInfo, error) { 179 if err := remote.load(); err != nil { 180 return nil, err 181 } 182 pkgInfos, err := remote.PackageInfosByCmdName(name) 183 if len(pkgInfos) == 0 { 184 return nil, fmt.Errorf("no package named %s found from remote registry", name) 185 } 186 if err != nil { 187 return nil, err 188 } 189 for _, info := range pkgInfos { 190 if info.Version == version { 191 return &info, nil 192 } 193 } 194 return nil, fmt.Errorf("no package named %s with version %s found from remote registry", name, version) 195 } 196 197 func (remote *defaultRemoteRepository) url(name string, version string) string { 198 // first get the default url using the remote base following the convention 199 pkgUrl := fmt.Sprintf("%s/%s", remote.repoBaseUrl, remote.pkgFilename(name, version)) 200 201 // now try to find the package info 202 pkgInfo, err := remote.findPackage(name, version) 203 if err != nil || pkgInfo == nil { 204 return pkgUrl 205 } 206 if pkgInfo.Url != "" { 207 pkgUrl = pkgInfo.Url 208 } 209 210 return pkgUrl 211 } 212 213 func (remote *defaultRemoteRepository) pkgFilename(name string, version string) string { 214 return fmt.Sprintf("%s-%s.pkg", name, version) 215 } 216 217 func (remote *defaultRemoteRepository) load() error { 218 if !remote.isLoaded() { 219 body, err := helper.LoadFile(fmt.Sprintf("%s/index.json", remote.repoBaseUrl)) 220 if err != nil { 221 log.Error("Cannot read remote packages index") 222 return err 223 } 224 225 var entries []PackageInfo 226 err = json.Unmarshal(body, &entries) 227 if err != nil { 228 log.Error("json parsing error") 229 return err 230 } 231 232 for _, pkg := range entries { 233 lst, exists := remote.PackagesByName[pkg.Name] 234 if exists { 235 lst = append(lst, pkg) 236 remote.PackagesByName[pkg.Name] = lst 237 } else { 238 newLst := make(PackagesByVersion, 0) 239 newLst = append(newLst, pkg) 240 remote.PackagesByName[pkg.Name] = newLst 241 } 242 } 243 244 // sort packages 245 for _, v := range remote.PackagesByName { 246 sort.Sort(v) 247 } 248 } 249 250 return nil 251 } 252 253 func (remote *defaultRemoteRepository) isLoaded() bool { 254 return len(remote.PackagesByName) > 0 255 }