diff --git a/artifact/jfrog.tar.gz b/artifact/jfrog.tar.gz deleted file mode 100644 index 001d181a..00000000 Binary files a/artifact/jfrog.tar.gz and /dev/null differ diff --git a/artifact/jfrog/artifactory.go b/artifact/jfrog/artifactory.go new file mode 100644 index 00000000..649dea63 --- /dev/null +++ b/artifact/jfrog/artifactory.go @@ -0,0 +1,56 @@ +/* + * 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" + "net/http" + "net/url" + + "github.com/nabbar/golib/artifact" + "github.com/nabbar/golib/artifact/client" + "github.com/nabbar/golib/errors" +) + +func NewArtifactory(ctx context.Context, Do func(req *http.Request) (*http.Response, error), uri, containName, regexName string, reposPath ...string) (artifact.Client, errors.Error) { + if u, e := url.Parse(uri); e != nil { + return nil, ErrorURLParse.ErrorParent(e) + } else { + a := &artifactoryModel{ + ClientHelper: client.ClientHelper{}, + Do: Do, + ctx: ctx, + endpoint: u, + path: reposPath, + name: containName, + regex: regexName, + } + + a.ClientHelper.F = a.ListReleases + + return a, nil + } +} diff --git a/artifact/jfrog/error.go b/artifact/jfrog/error.go new file mode 100644 index 00000000..b0dc395d --- /dev/null +++ b/artifact/jfrog/error.go @@ -0,0 +1,82 @@ +/* + * 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 ( + "github.com/nabbar/golib/artifact" + "github.com/nabbar/golib/errors" +) + +const ( + ErrorParamsEmpty errors.CodeError = iota + artifact.MIN_ARTIFACT_JFORG + ErrorURLParse + ErrorRequestInit + ErrorRequestDo + ErrorRequestResponse + ErrorRequestResponseBodyEmpty + ErrorRequestResponseBodyDecode + ErrorArtifactoryNotFound + ErrorArtifactoryDownload +) + +var isCodeError = false + +func IsCodeError() bool { + return isCodeError +} + +func init() { + isCodeError = errors.ExistInMapMessage(ErrorParamsEmpty) + errors.RegisterIdFctMessage(ErrorParamsEmpty, getMessage) +} + +func getMessage(code errors.CodeError) (message string) { + switch code { + case errors.UNK_ERROR: + return "" + case ErrorParamsEmpty: + return "given parameters is empty" + case ErrorURLParse: + return "endpoint of JFrog Artifactory seems to be not valid" + case ErrorRequestInit: + return "cannot create new request to JFrog Artifactory" + case ErrorRequestDo: + return "cannot send request to JFrog Artifactory" + case ErrorRequestResponse: + return "error on sending request to JFrog Artifactory" + case ErrorRequestResponseBodyEmpty: + return "empty response while requesting JFrog Artifactory" + case ErrorRequestResponseBodyDecode: + return "cannot decode response from JFrog Artifactory" + case ErrorArtifactoryNotFound: + return "the requested constrains to the release are not matching" + case ErrorArtifactoryDownload: + return "error on downloading artifact" + } + + return "" +} diff --git a/artifact/jfrog/model.go b/artifact/jfrog/model.go new file mode 100644 index 00000000..850de20b --- /dev/null +++ b/artifact/jfrog/model.go @@ -0,0 +1,349 @@ +/* + * 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/ioutil" + "net/http" + "net/url" + "path" + "regexp" + "strconv" + "strings" + "time" + + "github.com/hashicorp/go-version" + libart "github.com/nabbar/golib/artifact" + artcli "github.com/nabbar/golib/artifact/client" + liberr "github.com/nabbar/golib/errors" + libiot "github.com/nabbar/golib/ioutils" +) + +type artifactoryModel struct { + artcli.ClientHelper + Do func(req *http.Request) (*http.Response, error) + ctx context.Context + endpoint *url.URL + path []string + name string + 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{}) liberr.Error { + var ( + ctx context.Context + cnl context.CancelFunc + req *http.Request + rsp *http.Response + + e error + u *url.URL + ) + + defer func() { + if cnl != nil { + cnl() + } + + if rsp != nil && rsp.Body != nil { + _ = rsp.Body.Close() + } + + if req != nil && req.Body != nil { + _ = req.Body.Close() + } + }() + + //ctx, cnl = context.WithTimeout(a.ctx, libhtc.TIMEOUT_5_SEC) + ctx, cnl = context.WithCancel(a.ctx) + + 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.ErrorParent(e) + } + + if rsp, e = a.Do(req); e != nil { + return ErrorRequestDo.ErrorParent(e) + } + + if rsp.StatusCode >= 400 { + return ErrorRequestResponse.ErrorParent(fmt.Errorf("status: %v", rsp.Status)) + } + + if rsp.Body == nil { + return ErrorRequestResponseBodyEmpty.ErrorParent(fmt.Errorf("status: %v", rsp.Status)) + } + + if buf, e := ioutil.ReadAll(rsp.Body); e != nil { + return ErrorRequestResponseBodyDecode.ErrorParent(e) + } else if e = json.Unmarshal(buf, bodyResponse); e != nil { + return ErrorRequestResponseBodyDecode.ErrorParent(e) + } + + cnl() + cnl = nil + + return nil + +} + +func (a *artifactoryModel) getStorageList() (sto []ResponseStorage, err liberr.Error) { + var ( + lst = ResponseReposStorage{} + ) + + 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 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.ErrorParent(e) + } + + sto = append(sto, res) + } + + return sto, nil +} + +func (a *artifactoryModel) releasesAppendNotExist(releases version.Collection, vers *version.Version) version.Collection { + for _, k := range releases { + if k.Equal(vers) { + return releases + } + } + + return append(releases, vers) +} + +func (a *artifactoryModel) ListReleases() (releases version.Collection, err liberr.Error) { + var ( + r *regexp.Regexp + sto []ResponseStorage + ) + + if a.regex != "" { + r = regexp.MustCompile(a.regex) + } + + if sto, err = a.getStorageList(); err != nil { + return nil, err + } + + for _, f := range sto { + if a.name != "" && !strings.Contains(f.Path, a.name) { + continue + } + + if r != nil && !r.MatchString(f.Path) { + continue + } + + if v, e := version.NewVersion(f.Path); 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 *version.Version) (art *ResponseStorage, err liberr.Error) { + var ( + r1 *regexp.Regexp + r2 *regexp.Regexp + + sto []ResponseStorage + ) + + if a.regex != "" { + r1 = regexp.MustCompile(a.regex) + } + + if regexName != "" { + r2 = regexp.MustCompile(regexName) + } + + if sto, err = a.getStorageList(); err != nil { + return nil, err + } + + for _, f := range sto { + if a.name != "" && !strings.Contains(f.Path, a.name) { + continue + } + + if r1 != nil && !r1.MatchString(f.Path) { + continue + } + + if v, e := version.NewVersion(f.Path); 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 r2 != nil && !r2.MatchString(f.Path) { + continue + } else { + return &f, nil + } + } + + return nil, ErrorArtifactoryNotFound.Error(nil) +} + +func (a *artifactoryModel) GetArtifact(containName string, regexName string, release *version.Version) (link string, err liberr.Error) { + if art, err := a.getArtifact(containName, regexName, release); err != nil { + return "", err + } else { + return art.DownloadUri, nil + } +} + +func (a *artifactoryModel) Download(dst libiot.FileProgress, containName string, regexName string, release *version.Version) liberr.Error { + var ( + e error + + art *ResponseStorage + err liberr.Error + req *http.Request + rsp *http.Response + ) + + defer func() { + if rsp != nil && rsp.Body != nil { + _ = rsp.Body.Close() + } + + if req != nil && req.Body != nil { + _ = req.Body.Close() + } + }() + + if art, err = a.getArtifact(containName, regexName, release); err != nil { + return err + } + + dst.ResetMax(art.size) + + if req, e = http.NewRequestWithContext(a.ctx, http.MethodGet, art.DownloadUri, nil); e != nil { + return ErrorRequestInit.ErrorParent(e) + } else if rsp, e = a.Do(req); e != nil { + return ErrorRequestDo.ErrorParent(e) + } else if rsp.StatusCode >= 400 { + return ErrorRequestResponse.ErrorParent(fmt.Errorf("status: %v", rsp.Status)) + } else if rsp.Body == nil { + return ErrorRequestResponseBodyEmpty.ErrorParent(fmt.Errorf("status: %v", rsp.Status)) + } else if _, e := dst.ReadFrom(rsp.Body); e != nil { + return ErrorArtifactoryDownload.ErrorParent(e) + } + + return nil +} diff --git a/artifact/s3aws/model.go b/artifact/s3aws/model.go index a5010fa7..c449b056 100644 --- a/artifact/s3aws/model.go +++ b/artifact/s3aws/model.go @@ -58,7 +58,7 @@ func (s *s3awsModel) ListReleases() (releases version.Collection, err errors.Err ) if s.regex == "" { - return nil, ErrorParamsEmpty.Error(e) + return nil, ErrorParamsEmpty.Error(nil) } if l, e = s.c.Object().Find(s.regex); e != nil { diff --git a/go.mod b/go.mod index 6dc8f2b0..6d5c489b 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,12 @@ module github.com/nabbar/golib go 1.15 require ( - github.com/Masterminds/goutils v1.1.0 // indirect - github.com/Masterminds/semver v1.5.0 // indirect - github.com/Masterminds/sprig v2.22.0+incompatible // indirect - github.com/PuerkitoBio/goquery v1.6.1 // indirect github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect - github.com/andybalholm/cascadia v1.2.0 // indirect - github.com/aokoli/goutils v1.1.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.0.0 - github.com/aws/aws-sdk-go-v2/config v1.0.0 - github.com/aws/aws-sdk-go-v2/credentials v1.0.0 - github.com/aws/aws-sdk-go-v2/service/iam v1.0.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.0.0 + github.com/aws/aws-sdk-go-v2 v1.2.0 + github.com/aws/aws-sdk-go-v2/config v1.1.1 + github.com/aws/aws-sdk-go-v2/credentials v1.1.1 + github.com/aws/aws-sdk-go-v2/service/iam v1.1.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.2.0 github.com/fatih/color v1.10.0 github.com/gin-gonic/gin v1.6.3 github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect @@ -26,41 +20,33 @@ require ( github.com/gobuffalo/packr v1.30.1 github.com/golang/protobuf v1.4.3 // indirect github.com/google/go-github/v33 v33.0.0 - github.com/google/uuid v1.2.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.6.8 github.com/hashicorp/go-uuid v1.0.2 github.com/hashicorp/go-version v1.2.1 - github.com/huandu/xstrings v1.3.2 // indirect - github.com/imdario/mergo v0.3.11 // indirect github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 github.com/json-iterator/go v1.1.10 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/matcornic/hermes/v2 v2.1.0 // indirect github.com/mattn/go-runewidth v0.0.10 // indirect - github.com/mitchellh/copystructure v1.0.0 // indirect - github.com/mitchellh/reflectwalk v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/olekukonko/tablewriter v0.0.4 - github.com/onsi/ginkgo v1.14.2 - github.com/onsi/gomega v1.10.4 + github.com/olekukonko/tablewriter v0.0.5 + github.com/onsi/ginkgo v1.15.0 + github.com/onsi/gomega v1.10.5 github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.7.0 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/shirou/gopsutil v3.20.12+incompatible + github.com/shirou/gopsutil v3.21.1+incompatible github.com/sirupsen/logrus v1.7.0 github.com/spf13/jwalterweatherman v1.1.0 github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect - github.com/ugorji/go v1.2.3 // indirect - github.com/vanng822/go-premailer v1.9.0 // indirect + github.com/ugorji/go v1.2.4 // indirect github.com/vbauerster/mpb/v5 v5.4.0 - github.com/xanzy/go-gitlab v0.42.0 - github.com/xhit/go-simple-mail/v2 v2.7.0 // indirect + github.com/xanzy/go-gitlab v0.43.0 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad golang.org/x/net v0.0.0-20210119194325-5f4716e94777 - golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 + golang.org/x/oauth2 v0.0.0-20210210192628-66670185b0cd golang.org/x/sync v0.0.0-20201207232520-09787c993a3a - golang.org/x/sys v0.0.0-20210123231150-1d476976d117 // indirect + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect golang.org/x/text v0.3.5 // indirect golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect