/* * MIT License * * Copyright (c) 2020 Nicolas JUHEL * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ package jfrog import ( "context" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "net/url" "path" "regexp" "strconv" "strings" "time" hscvrs "github.com/hashicorp/go-version" libart "github.com/nabbar/golib/artifact" artcli "github.com/nabbar/golib/artifact/client" ) type artifactoryModel struct { artcli.ClientHelper Do func(req *http.Request) (*http.Response, error) ctx context.Context endpoint *url.URL path []string group int regex string } type ResponseChecksum struct { Md5 string Sha1 string Sha256 string } type ResponseStorage struct { Uri string DownloadUri string Repo string Path string RemoteUrl string Created time.Time CreatedBy string LastModified time.Time ModifiedBy string LastUpdated time.Time Size string size int64 MimeType string Checksums ResponseChecksum OriginalChecksums ResponseChecksum } type ResponseReposChildrenStorage struct { Uri string Folder bool } type ResponseReposStorage struct { Uri string Repo string Path string Created time.Time CreatedBy string LastModified time.Time ModifiedBy string LastUpdated time.Time Children []ResponseReposChildrenStorage } func (a *artifactoryModel) request(uri string, bodyResponse interface{}) error { var ( ctx context.Context cnl context.CancelFunc req *http.Request rsp *http.Response e error u *url.URL ) //ctx, cnl = context.WithTimeout(a.ctx, libhtc.TIMEOUT_5_SEC) ctx, cnl = context.WithCancel(a.ctx) defer func() { if cnl != nil { cnl() } }() u = &url.URL{ Scheme: a.endpoint.Scheme, Opaque: a.endpoint.Opaque, User: a.endpoint.User, Host: a.endpoint.Host, Path: a.endpoint.Path, RawPath: a.endpoint.RawPath, ForceQuery: a.endpoint.ForceQuery, RawQuery: a.endpoint.RawQuery, Fragment: a.endpoint.Fragment, RawFragment: a.endpoint.RawFragment, } u.Path += path.Join("api", "storage", path.Join(a.path...)) if uri != "" { u.Path += path.Join(uri) } u.Path = path.Clean(u.Path) if req, e = http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil); e != nil { return ErrorRequestInit.Error(e) } defer func() { if req != nil && req.Body != nil { _ = req.Body.Close() } }() if rsp, e = a.Do(req); e != nil { return ErrorRequestDo.Error(e) } defer func() { if rsp != nil && rsp.Body != nil { _ = rsp.Body.Close() } }() if rsp.StatusCode >= http.StatusBadRequest { //nolint #goerr113 return ErrorRequestResponse.Error(fmt.Errorf("status: %v", rsp.Status)) } if rsp.Body == nil { //nolint #goerr113 return ErrorRequestResponseBodyEmpty.Error(fmt.Errorf("status: %v", rsp.Status)) } if buf, e := ioutil.ReadAll(rsp.Body); e != nil { return ErrorRequestResponseBodyDecode.Error(e) } else if e = json.Unmarshal(buf, bodyResponse); e != nil { return ErrorRequestResponseBodyDecode.Error(e) } cnl() cnl = nil return nil } func (a *artifactoryModel) getStorageList() (sto []ResponseStorage, err error) { var ( lst = ResponseReposStorage{} reg = regexp.MustCompile(a.regex) ) if a.regex == "" { //nolint #goerr113 return nil, ErrorParamEmpty.Error(fmt.Errorf("regex is empty: %s", a.regex)) } if a.group < 1 { //nolint #goerr113 return nil, ErrorParamEmpty.Error(fmt.Errorf("group extracted from regex is empty: %s - %v", a.regex, a.group)) } if err = a.request("", &lst); err != nil { return nil, err } else if len(lst.Children) < 1 { return make([]ResponseStorage, 0), nil } sto = make([]ResponseStorage, 0) for _, c := range lst.Children { var ( e error res = ResponseStorage{} ) if c.Folder { continue } if !reg.MatchString(c.Uri) { continue } if err = a.request(c.Uri, &res); err != nil { return nil, err } if res.size, e = strconv.ParseInt(res.Size, 10, 64); e != nil { return nil, ErrorRequestResponseBodyDecode.Error(e) } sto = append(sto, res) } return sto, nil } func (a *artifactoryModel) releasesAppendNotExist(releases hscvrs.Collection, vers *hscvrs.Version) hscvrs.Collection { for _, k := range releases { if k.Equal(vers) { return releases } } return append(releases, vers) } func (a *artifactoryModel) ListReleases() (releases hscvrs.Collection, err error) { var ( reg = regexp.MustCompile(a.regex) sto []ResponseStorage ) if sto, err = a.getStorageList(); err != nil { return nil, err } for _, f := range sto { grp := reg.FindStringSubmatch(f.Path) if len(grp) < a.group { continue } if v, e := hscvrs.NewVersion(grp[a.group]); e != nil { continue } else if !libart.ValidatePreRelease(v) { continue } else { releases = a.releasesAppendNotExist(releases, v) } } return releases, nil } func (a *artifactoryModel) getArtifact(containName string, regexName string, release *hscvrs.Version) (art *ResponseStorage, err error) { var ( reg = regexp.MustCompile(a.regex) rg2 *regexp.Regexp sto []ResponseStorage ) if regexName != "" { rg2 = regexp.MustCompile(regexName) } if sto, err = a.getStorageList(); err != nil { return nil, err } for _, f := range sto { grp := reg.FindStringSubmatch(f.Path) if len(grp) < a.group { continue } if v, e := hscvrs.NewVersion(grp[a.group]); e != nil { continue } else if !libart.ValidatePreRelease(v) { continue } else if release != nil && !v.Equal(release) { continue } else if containName != "" && !strings.Contains(f.Path, containName) { continue } else if rg2 != nil && !rg2.MatchString(f.Path) { continue } else { return &f, nil } } return nil, ErrorArtifactoryNotFound.Error(nil) } func (a *artifactoryModel) GetArtifact(containName string, regexName string, release *hscvrs.Version) (link string, err error) { if art, err := a.getArtifact(containName, regexName, release); err != nil { return "", err } else { return art.DownloadUri, nil } } func (a *artifactoryModel) Download(containName string, regexName string, release *hscvrs.Version) (int64, io.ReadCloser, error) { var ( e error art *ResponseStorage err error req *http.Request rsp *http.Response ) defer func() { if req != nil && req.Body != nil { _ = req.Body.Close() } }() if art, err = a.getArtifact(containName, regexName, release); err != nil { return 0, nil, err } if req, e = http.NewRequestWithContext(a.ctx, http.MethodGet, art.DownloadUri, nil); e != nil { return 0, nil, ErrorRequestInit.Error(e) } else if rsp, e = a.Do(req); e != nil { return 0, nil, ErrorRequestDo.Error(e) } else if rsp.StatusCode >= http.StatusBadRequest { //nolint #goerr113 return 0, nil, ErrorRequestResponse.Error(fmt.Errorf("status: %v", rsp.Status)) } else if rsp.Body == nil { //nolint #goerr113 return 0, nil, ErrorRequestResponseBodyEmpty.Error(fmt.Errorf("status: %v", rsp.Status)) } else if art.size != rsp.ContentLength { _ = rsp.Body.Close() return 0, nil, ErrorDestinationSize.Error(errMisMatchingSize) } else { return rsp.ContentLength, rsp.Body, nil } }