This is an automated email from the ASF dual-hosted git repository. nferraro pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit fc9e085198644fa0491843fd1f066c1d810162f6 Author: lburgazzoli <[email protected]> AuthorDate: Sat Jun 6 10:25:30 2020 +0200 CLI - Make kamel run handle github urls that are not raw #1497 --- e2e/common/run_test.go | 19 +++++++++- go.mod | 1 + go.sum | 15 ++++++++ pkg/cmd/modeline.go | 2 +- pkg/cmd/run.go | 51 +++++++++++++-------------- pkg/cmd/util_getter.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 156 insertions(+), 28 deletions(-) diff --git a/e2e/common/run_test.go b/e2e/common/run_test.go index 391927d..77ebbbc 100644 --- a/e2e/common/run_test.go +++ b/e2e/common/run_test.go @@ -24,7 +24,6 @@ package common import ( "testing" - . "github.com/apache/camel-k/e2e/support" camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" @@ -44,6 +43,24 @@ func TestRunSimpleExamples(t *testing.T) { Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) }) + t.Run("run java from GitHub", func(t *testing.T) { + RegisterTestingT(t) + Expect(Kamel("run", "-n", ns, "github:apache/camel-k/e2e/common/files/Java.java").Execute()).Should(BeNil()) + Eventually(IntegrationPodPhase(ns, "java"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "java", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) + Eventually(IntegrationLogs(ns, "java"), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) + Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) + }) + + t.Run("run java from GitHub (RAW)", func(t *testing.T) { + RegisterTestingT(t) + Expect(Kamel("run", "-n", ns, "https://raw.githubusercontent.com/apache/camel-k/master/e2e/common/files/Java.java").Execute()).Should(BeNil()) + Eventually(IntegrationPodPhase(ns, "java"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "java", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) + Eventually(IntegrationLogs(ns, "java"), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) + Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) + }) + t.Run("run java with properties", func(t *testing.T) { RegisterTestingT(t) Expect(Kamel("run", "-n", ns, "files/Prop.java", "--property-file", "files/prop.properties").Execute()).Should(BeNil()) diff --git a/go.mod b/go.mod index 4261a95..7163200 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/gertd/go-pluralize v0.1.1 github.com/go-logr/logr v0.1.0 github.com/google/uuid v1.1.1 + github.com/hashicorp/go-getter v1.4.1 github.com/jpillora/backoff v1.0.0 github.com/magiconair/properties v1.8.1 github.com/mitchellh/go-homedir v1.1.0 diff --git a/go.sum b/go.sum index 7aa83dc..c5771d7 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,7 @@ cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNF cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.3.0 h1:2Ze/3nQD5F+HfL0xOPM2EeawDWs+NPRtzgcre+17iZU= cloud.google.com/go/storage v1.3.0/go.mod h1:9IAwXhoyBJ7z9LcAwkj0/7NnPzYaPeZxxVp3zm+5IqA= contrib.go.opencensus.io/exporter/ocagent v0.6.0 h1:Z1n6UAyr0QwM284yUuh5Zd8JlvxUGAhFZcgMJkMPrGM= contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs= @@ -121,6 +122,7 @@ github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:l github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.20 h1:2CBuL21P0yKdZN5urf2NxKa1ha8fhnY+A3pBCHFeZoA= @@ -135,6 +137,8 @@ github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= @@ -163,6 +167,7 @@ github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tj github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -486,7 +491,10 @@ github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-getter v1.4.1 h1:3A2Mh8smGFcf5M+gmcv898mZdrxpseik45IpcyISLsA= +github.com/hashicorp/go-getter v1.4.1/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= @@ -496,11 +504,14 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -634,6 +645,7 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= @@ -909,6 +921,8 @@ github.com/uber/jaeger-client-go v2.20.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMW github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= +github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c= @@ -1256,6 +1270,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= diff --git a/pkg/cmd/modeline.go b/pkg/cmd/modeline.go index 3a4f321..7d6a848 100644 --- a/pkg/cmd/modeline.go +++ b/pkg/cmd/modeline.go @@ -116,7 +116,7 @@ func createKamelWithModelineCommand(ctx context.Context, args []string, processe return nil, nil, errors.Wrapf(err, "cannot process file %s", f) } for i, o := range ops { - if fileOptions[o.Name] && !isRemoteHTTPFile(f) { + if fileOptions[o.Name] && isLocal(f) { refPath := o.Value if !filepath.IsAbs(refPath) { full := path.Join(baseDir, refPath) diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go index 31869d3..90f545d 100644 --- a/pkg/cmd/run.go +++ b/pkg/cmd/run.go @@ -23,13 +23,12 @@ import ( "fmt" "io/ioutil" - "net/http" + "net/url" "os" "os/signal" "path" "reflect" "regexp" - "strconv" "strings" "syscall" @@ -202,24 +201,17 @@ func (o *runCmdOptions) validateArgs(_ *cobra.Command, args []string) error { return errors.New("run expects at least 1 argument, received 0") } - for _, fileName := range args { - if !isRemoteHTTPFile(fileName) { - if _, err := os.Stat(fileName); err != nil && os.IsNotExist(err) { - return errors.Wrap(err, "file "+fileName+" does not exist") + for _, source := range args { + if isLocal(source) { + if _, err := os.Stat(source); err != nil && os.IsNotExist(err) { + return errors.Wrapf(err, "file %s does not exist", source) } else if err != nil { - return errors.Wrap(err, "error while accessing file "+fileName) + return errors.Wrapf(err, "error while accessing file %s", source) } } else { - // nolint: gosec - resp, err := http.Get(fileName) - if resp != nil && resp.Body != nil { - _ = resp.Body.Close() - } - + _, err := loadData(source, false) if err != nil { - return errors.Wrap(err, "The URL provided is not reachable") - } else if resp.StatusCode != 200 { - return errors.New("The URL provided is not reachable " + fileName + " The error code returned is " + strconv.Itoa(resp.StatusCode)) + return errors.Wrap(err, "The provided source is not reachable") } } } @@ -401,7 +393,7 @@ func (o *runCmdOptions) syncIntegration(c client.Client, sources []string) error files = append(files, o.OpenAPIs...) for _, s := range files { - if !isRemoteHTTPFile(s) { + if isLocal(s) { changes, err := sync.File(o.Context, s) if err != nil { return err @@ -638,24 +630,27 @@ func (o *runCmdOptions) GetIntegrationName(sources []string) string { return name } -func loadData(fileName string, compress bool) (string, error) { +func loadData(source string, compress bool) (string, error) { var content []byte var err error - if !isRemoteHTTPFile(fileName) { - content, err = ioutil.ReadFile(fileName) + if isLocal(source) { + content, err = ioutil.ReadFile(source) if err != nil { return "", err } } else { - /* #nosec */ - resp, err := http.Get(fileName) + u, err := url.Parse(source) if err != nil { return "", err } - defer resp.Body.Close() - content, err = ioutil.ReadAll(resp.Body) + g, ok := Getters[u.Scheme] + if !ok { + return "", fmt.Errorf("unable to find a getter for URL: %s", source) + } + + content, err = g.Get(u) if err != nil { return "", err } @@ -709,8 +704,12 @@ func (*runCmdOptions) configureTrait(integration *v1.Integration, config string) return nil } -func isRemoteHTTPFile(fileName string) bool { - return strings.HasPrefix(fileName, "http://") || strings.HasPrefix(fileName, "https://") +func isLocal(fileName string) bool { + info, err := os.Stat(fileName) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() } func addPropertyFile(fileName string, spec *v1.IntegrationSpec) error { diff --git a/pkg/cmd/util_getter.go b/pkg/cmd/util_getter.go new file mode 100644 index 0000000..f472396 --- /dev/null +++ b/pkg/cmd/util_getter.go @@ -0,0 +1,96 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "regexp" +) + +var Getters map[string]Getter + +func init() { + Getters = map[string]Getter{ + "http": HttpGetter{}, + "https": HttpGetter{}, + "github": GitHubGetter{}, + } +} + +type Getter interface { + Get(u *url.URL) ([]byte, error) +} + +// A simple getter that retrieves the content of an integration from an +// http(s) endpoint. +type HttpGetter struct { +} + +func (g HttpGetter) Get(u *url.URL) ([]byte, error) { + return g.doGet(u.String()) +} + +func (g HttpGetter) doGet(source string) ([]byte, error) { + // nolint: gosec + resp, err := http.Get(source) + if err != nil { + return []byte{}, err + } + defer func() { + _ = resp.Body.Close() + }() + + if resp.StatusCode != 200 { + return []byte{}, fmt.Errorf("the provided URL %s is not reachable, error code is %d", source, resp.StatusCode) + } + + content, err := ioutil.ReadAll(resp.Body) + if err != nil { + return []byte{}, err + } + + return content, nil +} + +// A simple getter that retrieves the content of an integration from +// a GitHub endpoint using a RAW endpoint. +type GitHubGetter struct { + HttpGetter +} + +func (g GitHubGetter) Get(u *url.URL) ([]byte, error) { + src := u.Scheme + ":" + u.Opaque + re := regexp.MustCompile(`^github:([^/]+)/([^/]+)/(.+)$`) + + items := re.FindStringSubmatch(src) + if len(items) != 4 { + return []byte{}, fmt.Errorf("malformed github url: %s", src) + } + + branch := u.Query().Get("branch") + if branch == "" { + branch = "master" + } + + srcURL := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/%s", items[1], items[2], branch, items[3]) + + return g.HttpGetter.doGet(srcURL) +}
