diff --git a/go.mod b/go.mod index 9ac85ea..2f8f2e6 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.26.4 github.com/charmbracelet/lipgloss v0.12.1 + github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be github.com/daixiang0/gci v0.13.4 github.com/flosch/pongo2/v6 v6.0.0 github.com/go-critic/go-critic v0.11.3 @@ -21,8 +22,10 @@ require ( github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/stretchr/testify v1.9.0 + github.com/xanzy/go-gitlab v0.107.0 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d + gopkg.in/dnaeon/go-vcr.v4 v4.0.1 gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/yaml.v2 v2.4.0 mvdan.cc/gofumpt v0.6.0 @@ -41,6 +44,7 @@ require ( github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/alecthomas/go-check-sumtype v0.1.4 // indirect github.com/alexflint/go-scalar v1.2.0 // indirect @@ -69,12 +73,11 @@ require ( github.com/charmbracelet/x/windows v0.1.2 // indirect github.com/chavacava/garif v0.1.0 // indirect github.com/ckaznocha/intrange v0.1.2 // indirect - github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect github.com/cristalhq/acmd v0.11.2 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect - github.com/emirpasic/gods v1.12.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.16.0 // indirect @@ -83,6 +86,7 @@ require ( github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/ghostiam/protogetter v0.3.6 // indirect + github.com/gliderlabs/ssh v0.3.7 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -123,7 +127,7 @@ require ( github.com/jjti/go-spancheck v0.6.1 // indirect github.com/julz/importas v0.1.0 // indirect github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect - github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kisielk/errcheck v1.7.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/kulti/thelper v0.6.3 // indirect @@ -179,7 +183,7 @@ require ( github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect github.com/securego/gosec/v2 v2.19.0 // indirect - github.com/sergi/go-diff v1.0.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect @@ -207,8 +211,7 @@ require ( github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.1 // indirect github.com/uudashr/gocognit v1.1.2 // indirect - github.com/xanzy/go-gitlab v0.107.0 // indirect - github.com/xanzy/ssh-agent v0.2.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yagipy/maintidx v1.0.0 // indirect diff --git a/go.sum b/go.sum index 8df60b6..a1359f2 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,9 @@ github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8M github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= @@ -82,8 +85,9 @@ github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pO github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0= github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA= github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= @@ -169,8 +173,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= -github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -198,8 +203,9 @@ github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk= github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= -github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-critic/go-critic v0.11.3 h1:SJbYD/egY1noYjTMNTlhGaYlfQ77rQmrNH7h+gtn0N0= github.com/go-critic/go-critic v0.11.3/go.mod h1:Je0h5Obm1rR5hAGA9mP2PDiOOk53W+n7pyvXErFKIgI= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -341,6 +347,8 @@ github.com/gotesttools/gotestfmt/v2 v2.5.0 h1:fSU3MnR+E+fvuXdw1l8xbufKhDxY3Tfjsj github.com/gotesttools/gotestfmt/v2 v2.5.0/go.mod h1:oQJg2KZ2aGoqEbMC2PDaAeBYm0tOkocgixK9FzsCdp4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -384,8 +392,9 @@ github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= -github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -570,8 +579,9 @@ github.com/sashamelentyev/usestdlibvars v1.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9 github.com/sashamelentyev/usestdlibvars v1.25.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= github.com/securego/gosec/v2 v2.19.0 h1:gl5xMkOI0/E6Hxx0XCY2XujA3V7SNSefA8sC+3f1gnk= github.com/securego/gosec/v2 v2.19.0/go.mod h1:hOkDcHz9J/XIgIlPDXalxjeVYsHxoWUc5zJSHxcB8YM= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= @@ -579,6 +589,7 @@ github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOms github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= @@ -663,8 +674,9 @@ github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvni github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= github.com/xanzy/go-gitlab v0.107.0 h1:P2CT9Uy9yN9lJo3FLxpMZ4xj6uWcpnigXsjvqJ6nd2Y= github.com/xanzy/go-gitlab v0.107.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY= -github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= @@ -715,6 +727,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= @@ -800,6 +813,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= @@ -846,6 +860,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1068,6 +1083,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/dnaeon/go-vcr.v4 v4.0.1 h1:dIFuOqqDZIJ9BTcK+DXmElzypQ6PV9fBQZSIwY+J1yM= +gopkg.in/dnaeon/go-vcr.v4 v4.0.1/go.mod h1:65yxh9goQVrudqofKtHA4JNFWd6XZRkWfKN4YpMx7KI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= diff --git a/internal/configuration/configfile.go b/internal/configuration/configfile.go index daf15b0..547033b 100644 --- a/internal/configuration/configfile.go +++ b/internal/configuration/configfile.go @@ -33,7 +33,7 @@ func (cf *ConfigFile) GetFilePath() string { } func (cf *ConfigFile) WriteConfig(configData []byte) error { - cf.log.WithFields(log.Fields{"filePath": cf.filePath}).Debug("Writing configuration to file") + cf.log.WithFields(log.Fields{"filePath": cf.filePath}).Debug("writing configuration to file") if err := os.WriteFile(cf.filePath, configData, 0o644); err != nil { return err } @@ -41,6 +41,6 @@ func (cf *ConfigFile) WriteConfig(configData []byte) error { } func (cf *ConfigFile) ReadConfig() ([]byte, error) { - cf.log.WithFields(log.Fields{"filePath": cf.filePath}).Debug("Reading configuration from file") + cf.log.WithFields(log.Fields{"filePath": cf.filePath}).Debug("reading configuration from file") return os.ReadFile(cf.filePath) } diff --git a/internal/contentprovider/fixtures/content-provider-github.yaml b/internal/contentprovider/fixtures/content-provider-github.yaml new file mode 100644 index 0000000..5e35e22 --- /dev/null +++ b/internal/contentprovider/fixtures/content-provider-github.yaml @@ -0,0 +1,164 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: github.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - '*/*' + Host: + - github.com + User-Agent: + - git/1.0 + url: https://github.com/gchiesa/ska-example-template/info/refs?service=git-upload-pack + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: -1 + uncompressed: false + body: "001e# service=git-upload-pack\n00000153a3086fc06ba53f04932f9e2cf1b3e5ad8f2dd006 HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress include-tag multi_ack_detailed allow-tip-sha1-in-want allow-reachable-sha1-in-want no-done symref=HEAD:refs/heads/main filter object-format=sha1 agent=git/github-254539cbab51\n003da3086fc06ba53f04932f9e2cf1b3e5ad8f2dd006 refs/heads/main\n0000" + headers: + Cache-Control: + - no-cache, max-age=0, must-revalidate + Content-Security-Policy: + - default-src 'none'; sandbox + Content-Type: + - application/x-git-upload-pack-advertisement + Date: + - Fri, 30 Aug 2024 12:46:23 GMT + Expires: + - Fri, 01 Jan 1980 00:00:00 GMT + Pragma: + - no-cache + Server: + - GitHub-Babel/3.0 + Vary: + - Accept-Encoding + X-Frame-Options: + - DENY + X-Github-Request-Id: + - D88C:2A7A97:D062D8:D35C20:66D1BF1F + status: 200 OK + code: 200 + duration: 176.250417ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 116 + transfer_encoding: [] + trailer: {} + host: github.com + remote_addr: "" + request_uri: "" + body: | + 0067want a3086fc06ba53f04932f9e2cf1b3e5ad8f2dd006 side-band-64k ofs-delta agent=go-git/4.x no-progress + 00000009done + form: {} + headers: + Accept: + - application/x-git-upload-pack-result + Content-Length: + - "116" + Content-Type: + - application/x-git-upload-pack-request + Host: + - github.com + User-Agent: + - git/1.0 + url: https://github.com/gchiesa/ska-example-template/git-upload-pack + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: -1 + uncompressed: false + body: !!binary | + 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 + headers: + Cache-Control: + - no-cache, max-age=0, must-revalidate + Content-Security-Policy: + - default-src 'none'; sandbox + Content-Type: + - application/x-git-upload-pack-result + Date: + - Fri, 30 Aug 2024 12:46:23 GMT + Expires: + - Fri, 01 Jan 1980 00:00:00 GMT + Pragma: + - no-cache + Server: + - GitHub-Babel/3.0 + Vary: + - Accept-Encoding + X-Frame-Options: + - DENY + X-Github-Request-Id: + - D88C:2A7A97:D0643F:D35DA0:66D1BF1F + status: 200 OK + code: 200 + duration: 128.54375ms diff --git a/internal/contentprovider/fixtures/content-provider-gitlab.yaml b/internal/contentprovider/fixtures/content-provider-gitlab.yaml new file mode 100644 index 0000000..d2dc82b --- /dev/null +++ b/internal/contentprovider/fixtures/content-provider-gitlab.yaml @@ -0,0 +1,104 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: gitlab.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Private-Token: + - "" + User-Agent: + - go-gitlab + url: https://gitlab.com/api/v4/projects/gchiesa%2Ftest/repository/archive.zip?sha=master + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: -1 + uncompressed: false + body: !!binary | + UEsDBAoAAAAAAKCAH0sAAAAAAAAAAAAAAAA1AAkAdGVzdC1tYXN0ZXItM2U2NDcxYzk1NT + A3YjRjNGNhNjI2Yzk5YTRmMjY5Y2RmOTAyNjBkNy9VVAUAAa0zqFlQSwMECgAAAAAAoIAf + SwAAAAAAAAAAAAAAAD4ACQB0ZXN0LW1hc3Rlci0zZTY0NzFjOTU1MDdiNGM0Y2E2MjZjOT + lhNGYyNjljZGY5MDI2MGQ3L1JFQURNRS5tZFVUBQABrTOoWVBLAwQKAAAAAACggB9LkGA4 + Sw0AAAANAAAAPgAJAHRlc3QtbWFzdGVyLTNlNjQ3MWM5NTUwN2I0YzRjYTYyNmM5OWE0Zj + I2OWNkZjkwMjYwZDcvc29tZXRoaW5nVVQFAAGtM6hZZW1wdHkuLi4gbm90ClBLAwQKAAAA + CACggB9LcOI9ay8AAAA0AAAASgAJAHRlc3QtbWFzdGVyLTNlNjQ3MWM5NTUwN2I0YzRjYT + YyNmM5OWE0ZjI2OWNkZjkwMjYwZDcveW91LXdvbnQtYmVsaWV2ZS10aGlzVVQFAAGtM6hZ + K8nILFYAokSFnMy8VK7EvPyU1CIwW0EjJV+hMr9UoTg1VaEkA4grC/LtFaw0uQBQSwECAA + AKAAAAAACggB9LAAAAAAAAAAAAAAAANQAJAAAAAAAAABAAAAAAAAAAdGVzdC1tYXN0ZXIt + M2U2NDcxYzk1NTA3YjRjNGNhNjI2Yzk5YTRmMjY5Y2RmOTAyNjBkNy9VVAUAAa0zqFlQSw + ECAAAKAAAAAACggB9LAAAAAAAAAAAAAAAAPgAJAAAAAAABAAAAAABcAAAAdGVzdC1tYXN0 + ZXItM2U2NDcxYzk1NTA3YjRjNGNhNjI2Yzk5YTRmMjY5Y2RmOTAyNjBkNy9SRUFETUUubW + RVVAUAAa0zqFlQSwECAAAKAAAAAACggB9LkGA4Sw0AAAANAAAAPgAJAAAAAAABAAAAAADB + AAAAdGVzdC1tYXN0ZXItM2U2NDcxYzk1NTA3YjRjNGNhNjI2Yzk5YTRmMjY5Y2RmOTAyNj + BkNy9zb21ldGhpbmdVVAUAAa0zqFlQSwECAAAKAAAACACggB9LcOI9ay8AAAA0AAAASgAJ + AAAAAAABAAAAAAAzAQAAdGVzdC1tYXN0ZXItM2U2NDcxYzk1NTA3YjRjNGNhNjI2Yzk5YT + RmMjY5Y2RmOTAyNjBkNy95b3Utd29udC1iZWxpZXZlLXRoaXNVVAUAAa0zqFlQSwUGAAAA + AAQABADXAQAA0wEAACgAM2U2NDcxYzk1NTA3YjRjNGNhNjI2Yzk5YTRmMjY5Y2RmOTAyNj + BkNw== + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Cf-Cache-Status: + - MISS + Cf-Ray: + - 8bb4e27788de4266-AMS + Content-Disposition: + - attachment; filename="test-master-3e6471c95507b4c4ca626c99a4f269cdf90260d7.zip" + Content-Security-Policy: + - default-src 'none' + Content-Transfer-Encoding: + - binary + Content-Type: + - application/zip + Date: + - Fri, 30 Aug 2024 12:46:37 GMT + Etag: + - W/"12ae32cb1ec02d01eda3581b127c1fee" + Gitlab-Lb: + - haproxy-main-24-lb-gprd + Gitlab-Sv: + - gke-cny-api + Nel: + - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=OodJJVR%2F0xN1lCY0xcJCLmtxpvk6CV0%2BbcKFqG9tnPwW0WYnQShWpUzxQSxSYpZYGWswrqPd%2FylP29oARAY3TN8uh40mK5iG0xBbUXxthnRyw7l6IkjEMrDW4CY%3D"}],"group":"cf-nel","max_age":604800}' + Server: + - cloudflare + Set-Cookie: + - _cfuvid=uLtEt2EsftPOYwlWAFamg9YmrFUSq.vI58OQB.Wzlos-1725021997316-0.0.1.1-604800000; path=/; domain=.gitlab.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Origin, Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Gitlab-Meta: + - '{"correlation_id":"13d0980c4f908f96193feadab20cafe9","version":"1"}' + X-Request-Id: + - 13d0980c4f908f96193feadab20cafe9 + X-Runtime: + - "0.424257" + status: 200 OK + code: 200 + duration: 646.394875ms diff --git a/internal/contentprovider/github.go b/internal/contentprovider/github.go index 07e578f..ab8e700 100644 --- a/internal/contentprovider/github.go +++ b/internal/contentprovider/github.go @@ -7,14 +7,16 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/transport/http" "os" + "path/filepath" ) type GitHub struct { - remoteURI string - repositoryURL string - repositoryRef string - workingDir string - log *log.Entry + remoteURI string + repositoryURL string + repositoryRef string + repositoryFilePath string + workingDir string + log *log.Entry } const GitHubPrefix = "https://github.com/" @@ -32,6 +34,12 @@ func NewGitHub(remoteURI string) (*GitHub, error) { } func (cp *GitHub) WorkingDir() string { + // if filepath is set we set that one as working directory + if cp.repositoryFilePath != "" { + wd := filepath.Join(cp.workingDir, cp.repositoryFilePath) + cp.log.WithFields(log.Fields{"repositoryFilePath": cp.repositoryFilePath}).Debugf("using repository file path: %s", wd) + return wd + } return cp.workingDir } @@ -79,6 +87,7 @@ func (cp *GitHub) DownloadContent() error { if err := cp.removeGitFolder(); err != nil { return err } + return nil } diff --git a/internal/contentprovider/github_internal.go b/internal/contentprovider/github_internal.go index 41d468d..c95d6bf 100644 --- a/internal/contentprovider/github_internal.go +++ b/internal/contentprovider/github_internal.go @@ -11,7 +11,7 @@ import ( ) func (cp *GitHub) validateRemoteURI(string) error { - url, ref := parseRemoteURI(cp.remoteURI) + url, filePath, ref := parseRemoteURIV2(cp.remoteURI) if !strings.HasPrefix(url, "https://github.com/") { return errors.New("invalid github url. The url must start with https://github.com/") } @@ -20,6 +20,7 @@ func (cp *GitHub) validateRemoteURI(string) error { } cp.repositoryURL = url cp.repositoryRef = ref + cp.repositoryFilePath = filePath return nil } diff --git a/internal/contentprovider/github_test.go b/internal/contentprovider/github_test.go new file mode 100644 index 0000000..1bdfeb2 --- /dev/null +++ b/internal/contentprovider/github_test.go @@ -0,0 +1,81 @@ +package contentprovider + +import ( + "github.com/apex/log" + "github.com/stretchr/testify/assert" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" + "gopkg.in/src-d/go-git.v4/plumbing/transport/client" + githttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http" + "testing" +) + +const ( + GitHubTestRepository = "https://github.com/gchiesa/ska-example-template@main" +) + +func TestGitHubDownloadContent(t *testing.T) { + r, err := recorder.New("fixtures/content-provider-github") + if err != nil { + log.Fatalf("error creating recorder: %v", err) + } + defer func(r *recorder.Recorder) { _ = r.Stop() }(r) + + // fake httpClient + httpClient := r.GetDefaultClient() + + // use the mock client + client.InstallProtocol("https", githttp.NewClient(httpClient)) + + gh, err := NewGitHub(GitHubTestRepository) + if err != nil { + log.Fatalf("error creating GitHub client: %v", err) + } + err = gh.DownloadContent() + assert.NoError(t, err) +} + +func TestGithub_validateRemoteURI(t *testing.T) { + testcases := []struct { + name string + uri string + expectedTupleFunc func(t *testing.T, cp *GitHub) + expectedErrFunc func(t *testing.T, err error) + }{ + {"Given URI without branch should return an error", + "https://github.com/gchiesa/test", + func(t *testing.T, cp *GitHub) { return }, + func(t *testing.T, err error) { assert.Error(t, err) }, + }, + { + "Given URI with branch should return the tuple and no errors with filePath empty", + "https://github.com/gchiesa/test@branch123", + func(t *testing.T, cp *GitHub) { + assert.NotEmpty(t, cp.remoteURI) + assert.Equal(t, "branch123", cp.repositoryRef) + assert.Empty(t, cp.repositoryFilePath) + }, + func(t *testing.T, err error) { assert.NoError(t, err) }, + }, + { + "Given URI with filepath and reference, the tuple should match the parts and no errors", + "https://github.com/gchiesa/test//path_from_root/file1@main", + func(t *testing.T, cp *GitHub) { + assert.NotEmpty(t, cp.remoteURI) + assert.NotEmpty(t, cp.repositoryRef) + assert.Equal(t, "path_from_root/file1", cp.repositoryFilePath) + }, + func(t *testing.T, err error) { assert.NoError(t, err) }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + cp, err := NewGitHub(tc.uri) + assert.NoError(t, err) + err = cp.validateRemoteURI(tc.uri) + tc.expectedTupleFunc(t, cp) + tc.expectedErrFunc(t, err) + }) + } + +} diff --git a/internal/contentprovider/gitlab.go b/internal/contentprovider/gitlab.go index a9f0704..8223b7c 100644 --- a/internal/contentprovider/gitlab.go +++ b/internal/contentprovider/gitlab.go @@ -1,28 +1,29 @@ package contentprovider import ( - "archive/zip" - "fmt" "github.com/apex/log" "github.com/xanzy/go-gitlab" - "io" + "net/http" "os" "path/filepath" - "strings" ) type GitLab struct { - remoteURI string - repositoryURL string - repositoryRef string - projectPath string - workingDir string - log *log.Entry + remoteURI string + repositoryURL string + repositoryRef string + repositoryFilePath string + projectPath string + workingDir string + gitlabOptions []gitlab.ClientOptionFunc + log *log.Entry } +type GitLabOption func(*GitLab) + const GitLabPrefix = "https://gitlab.com/" -func NewGitLab(remoteURI string) (*GitLab, error) { +func NewGitLab(remoteURI string, opt ...GitLabOption) (*GitLab, error) { logCtx := log.WithFields(log.Fields{ "pkg": "contentprovider", "type": "gitlab", @@ -31,10 +32,29 @@ func NewGitLab(remoteURI string) (*GitLab, error) { if err != nil { return nil, err } - return &GitLab{remoteURI: remoteURI, workingDir: tmpDir, log: logCtx}, nil + + gl := &GitLab{remoteURI: remoteURI, workingDir: tmpDir, log: logCtx} + + // set options + for _, opt := range opt { + opt(gl) + } + return gl, nil +} + +func WithHTTPClient(httpClient *http.Client) GitLabOption { + return func(cp *GitLab) { + cp.gitlabOptions = append(cp.gitlabOptions, gitlab.WithHTTPClient(httpClient)) + } } func (cp *GitLab) WorkingDir() string { + // if filepath is set we set that one as working directory + if cp.repositoryFilePath != "" { + wd := filepath.Join(cp.workingDir, cp.repositoryFilePath) + cp.log.WithFields(log.Fields{"repositoryFilePath": cp.repositoryFilePath}).Debugf("using repository file path: %s", wd) + return wd + } return cp.workingDir } @@ -48,48 +68,6 @@ func (cp *GitLab) RemoteURI() string { return cp.remoteURI } -func (cp *GitLab) downloadRepoZipArchive() (zipArchive string, err error) { - token := os.Getenv("GITLAB_PRIVATE_TOKEN") - - gitlabClient, err := gitlab.NewClient(token) - if err != nil { - return "", err - } - - tmpArchive, err := os.CreateTemp(os.TempDir(), "gitlab-repo-") - defer func(f *os.File) { _ = f.Close() }(tmpArchive) - - if err != nil { - return "", err - } - - var archiveFormat = "zip" - archiveOptions := &gitlab.ArchiveOptions{ - Format: &archiveFormat, - SHA: &cp.repositoryRef, - } - data, resp, err := gitlabClient.Repositories.Archive(cp.projectPath, archiveOptions, gitlab.WithToken(gitlab.PrivateToken, token)) - - if resp.StatusCode == 404 { - return "", fmt.Errorf("repository not found, perhaps you need to setup your private token for GitLab by exporting the environment variable GITLAB_PRIVATE_TOKEN. Status Code: %d", resp.StatusCode) - } - - if err != nil { - return "", err - } - - if resp.StatusCode != 200 { - return "", fmt.Errorf("invalid status code returned from GitLab API. Status Code: %d", resp.StatusCode) - } - - // write data to tmp archive - if _, err := tmpArchive.Write(data); err != nil { - return "", err - } - - return tmpArchive.Name(), nil -} - func (cp *GitLab) DownloadContent() error { if err := cp.validateRemoteURI(); err != nil { return err @@ -109,53 +87,6 @@ func (cp *GitLab) DownloadContent() error { if err := os.RemoveAll(tmpArchivePath); err != nil { return err } - return nil -} -func (cp *GitLab) unzipArchive(dst, archivePath string) error { - archive, err := zip.OpenReader(archivePath) - if err != nil { - return err - } - defer func(*zip.ReadCloser) { _ = archive.Close() }(archive) - - rootPath := "" - for _, item := range archive.File { - dstFilePath := filepath.Join(dst, item.Name) //nolint:gosec - - if !strings.HasPrefix(dstFilePath, filepath.Clean(dst)+string(os.PathSeparator)) { - return fmt.Errorf("invalid file path: %s", dstFilePath) - } - if item.FileInfo().IsDir() { - if err := os.MkdirAll(dstFilePath, os.ModePerm); err != nil { - return err - } - if rootPath == "" { - rootPath = item.Name - } - continue - } - if err := os.MkdirAll(filepath.Dir(dstFilePath), os.ModePerm); err != nil { - return err - } - - // open the dstFilePath as file - dstFile, err := os.OpenFile(dstFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, item.Mode()) - if err != nil { - panic(err) - } - - compressedFile, err := item.Open() - if err != nil { - return err - } - - if _, err := io.Copy(dstFile, compressedFile); err != nil { //nolint:gosec - return err - } - _ = dstFile.Close() - _ = compressedFile.Close() - } - cp.workingDir = filepath.Join(dst, rootPath) return nil } diff --git a/internal/contentprovider/gitlab_internal.go b/internal/contentprovider/gitlab_internal.go index 746f043..90453a3 100644 --- a/internal/contentprovider/gitlab_internal.go +++ b/internal/contentprovider/gitlab_internal.go @@ -1,12 +1,17 @@ package contentprovider import ( + "archive/zip" "fmt" + "github.com/xanzy/go-gitlab" + "io" + "os" + "path/filepath" "strings" ) func (cp *GitLab) validateRemoteURI() error { - url, ref := parseRemoteURI(cp.remoteURI) + url, filePath, ref := parseRemoteURIV2(cp.remoteURI) if !strings.HasPrefix(url, GitLabPrefix) { return fmt.Errorf("invalid github url. The url must start with: %s", GitLabPrefix) } @@ -15,6 +20,97 @@ func (cp *GitLab) validateRemoteURI() error { } cp.repositoryURL = url cp.repositoryRef = ref + cp.repositoryFilePath = filePath cp.projectPath = strings.TrimPrefix(cp.repositoryURL, GitLabPrefix) return nil } + +func (cp *GitLab) downloadRepoZipArchive() (zipArchive string, err error) { + token := os.Getenv("GITLAB_PRIVATE_TOKEN") + + gitlabClient, err := gitlab.NewClient(token, cp.gitlabOptions...) + if err != nil { + return "", err + } + + tmpArchive, err := os.CreateTemp(os.TempDir(), "gitlab-repo-") + defer func(f *os.File) { _ = f.Close() }(tmpArchive) + + if err != nil { + return "", err + } + + var archiveFormat = "zip" + archiveOptions := &gitlab.ArchiveOptions{ + Format: &archiveFormat, + SHA: &cp.repositoryRef, + } + data, resp, err := gitlabClient.Repositories.Archive(cp.projectPath, archiveOptions, gitlab.WithToken(gitlab.PrivateToken, token)) + + if resp.StatusCode == 404 { + return "", fmt.Errorf("repository not found, perhaps you need to setup your private token for GitLab by exporting the environment variable GITLAB_PRIVATE_TOKEN. Status Code: %d", resp.StatusCode) + } + + if err != nil { + return "", err + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("invalid status code returned from GitLab API. Status Code: %d", resp.StatusCode) + } + + // write data to tmp archive + if _, err := tmpArchive.Write(data); err != nil { + return "", err + } + + return tmpArchive.Name(), nil +} + +func (cp *GitLab) unzipArchive(dst, archivePath string) error { + archive, err := zip.OpenReader(archivePath) + if err != nil { + return err + } + defer func(*zip.ReadCloser) { _ = archive.Close() }(archive) + + rootPath := "" + for _, item := range archive.File { + dstFilePath := filepath.Join(dst, item.Name) //nolint:gosec + + if !strings.HasPrefix(dstFilePath, filepath.Clean(dst)+string(os.PathSeparator)) { + return fmt.Errorf("invalid file path: %s", dstFilePath) + } + if item.FileInfo().IsDir() { + if err := os.MkdirAll(dstFilePath, os.ModePerm); err != nil { + return err + } + if rootPath == "" { + rootPath = item.Name + } + continue + } + if err := os.MkdirAll(filepath.Dir(dstFilePath), os.ModePerm); err != nil { + return err + } + + // open the dstFilePath as file + dstFile, err := os.OpenFile(dstFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, item.Mode()) + if err != nil { + panic(err) + } + + compressedFile, err := item.Open() + if err != nil { + return err + } + + if _, err := io.Copy(dstFile, compressedFile); err != nil { //nolint:gosec + return err + } + _ = dstFile.Close() + _ = compressedFile.Close() + } + cp.workingDir = filepath.Join(dst, rootPath) + return nil +} diff --git a/internal/contentprovider/gitlab_test.go b/internal/contentprovider/gitlab_test.go index 9975ef3..d146044 100644 --- a/internal/contentprovider/gitlab_test.go +++ b/internal/contentprovider/gitlab_test.go @@ -1,16 +1,28 @@ package contentprovider import ( + "github.com/apex/log" "github.com/stretchr/testify/assert" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" "testing" ) -const GitLabTestPublicRepo = "https://gitlab.com/gchiesa/test@master" +const ( + GitLabTestRepository = "https://gitlab.com/gchiesa/test@master" +) + +func TestGitLabDownloadContent(t *testing.T) { + r, err := recorder.New("fixtures/content-provider-gitlab") + if err != nil { + log.Fatalf("error creating recorder: %v", err) + } + defer func(r *recorder.Recorder) { _ = r.Stop() }(r) + + // fake httpClient + httpClient := r.GetDefaultClient() -func TestGitLab_DownloadPublicRepo(t *testing.T) { - var err error - cp, err := NewGitLab(GitLabTestPublicRepo) - assert.NoErrorf(t, err, "error creating gitlab provider: %s", err) + cp, err := NewGitLab(GitLabTestRepository, WithHTTPClient(httpClient)) + assert.NoErrorf(t, err, "error creating GitLab client: %v", err) err = cp.DownloadContent() assert.NoErrorf(t, err, "error downloading content: %s", err) diff --git a/internal/contentprovider/util.go b/internal/contentprovider/util.go index 66ac71a..a883330 100644 --- a/internal/contentprovider/util.go +++ b/internal/contentprovider/util.go @@ -1,10 +1,14 @@ package contentprovider import ( + "fmt" "github.com/huandu/xstrings" + "strings" ) -func parseRemoteURI(uri string) (url, tag string) { - url, _, tag = xstrings.Partition(uri, "@") - return url, tag +func parseRemoteURIV2(uri string) (url, filePath, tag string) { + urlWithPath, _, tag := xstrings.Partition(uri, "@") + urlWithNoSchema := strings.TrimPrefix(urlWithPath, "https://") + urlWithNoPath, _, path := xstrings.Partition(urlWithNoSchema, "//") + return fmt.Sprintf("https://%s", urlWithNoPath), path, tag } diff --git a/internal/processor/internal.go b/internal/processor/internal.go index f6a76b5..9697bc1 100644 --- a/internal/processor/internal.go +++ b/internal/processor/internal.go @@ -119,10 +119,10 @@ func (tp *FileTreeProcessor) loadMultiparts() error { if err != nil { return err } - logger.WithFields(log.Fields{"parts": files, "multipart": relPath}).Debug("Generating Parts from Multipart.") + logger.WithFields(log.Fields{"parts": files, "multipart": relPath}).Debug("generating Parts from Multipart.") tp.multiparts = append(tp.multiparts, multipart) } else { - logger.WithFields(log.Fields{"filePath": relPath}).Debug("Skipping because is a directory.") + logger.WithFields(log.Fields{"filePath": relPath}).Debug("skipping because is a directory.") } return nil }) @@ -154,7 +154,7 @@ func (tp *FileTreeProcessor) renderStagingFileTree(withVariables map[string]inte if !info.IsDir() { // check if the file is to process if tp.multipartExistsAndHasPartials(relPath) { - logger.WithFields(log.Fields{"filePath": relPath}).Debug("Skipping file because it's a Multipart with Parts.") + logger.WithFields(log.Fields{"filePath": relPath}).Debug("skipping file because it's a Multipart with Parts.") return nil } @@ -171,12 +171,12 @@ func (tp *FileTreeProcessor) renderStagingFileTree(withVariables map[string]inte return err } - logger.WithFields(log.Fields{"filePath": relPath}).Debug("Saving rendered file.") + logger.WithFields(log.Fields{"filePath": relPath}).Debug("saving rendered file.") if err := os.WriteFile(absPath, []byte(buff.String()), 0o644); err != nil { //nolint:gosimple // we don't need to check the error here return err } } else { - logger.WithFields(log.Fields{"filePath": relPath}).Debug("Skipping because is a directory directory.") + logger.WithFields(log.Fields{"filePath": relPath}).Debug("skipping because is a directory directory.") } return nil @@ -269,12 +269,12 @@ func (tp *FileTreeProcessor) updateDestinationFileTree() error { if !info.IsDir() { // is it is not a swanson file we copy to destination if !tp.shouldProcessFile(relPath, tp.destinationIgnorePaths) { - logger.WithFields(log.Fields{"filePath": relPath}).Debug("Skipping file because should not be processed.") + logger.WithFields(log.Fields{"filePath": relPath}).Debug("skipping file because should not be processed.") return nil } // not a managed file then we copy it to destination if !tp.fileIsMultipart(relPath) { - logger.WithFields(log.Fields{"filePath": relPath, "destination": tp.destinationPathRoot}).Debug("Copying file to destination.") + logger.WithFields(log.Fields{"filePath": relPath, "destination": tp.destinationPathRoot}).Debug("copying file to destination.") if err := copy.Copy(absPath, filepath.Join(tp.destinationPathRoot, relPath)); err != nil { return err } @@ -284,7 +284,7 @@ func (tp *FileTreeProcessor) updateDestinationFileTree() error { // if it has no partials then we just copy as normal expanded file if !mp.HasParts() { - logger.WithFields(log.Fields{"filePath": relPath, "destination": tp.destinationPathRoot}).Debug("Copying non multipart file to destination.") + logger.WithFields(log.Fields{"filePath": relPath, "destination": tp.destinationPathRoot}).Debug("copying non multipart file to destination.") if err := copy.Copy(absPath, filepath.Join(tp.destinationPathRoot, relPath)); err != nil { return err } @@ -292,7 +292,7 @@ func (tp *FileTreeProcessor) updateDestinationFileTree() error { } // assemble back the partial container with the rendered partials - logger.WithFields(log.Fields{"filePath": relPath, "destination": tp.destinationPathRoot}).Debug("Compiling Multipart file to destination.") + logger.WithFields(log.Fields{"filePath": relPath, "destination": tp.destinationPathRoot}).Debug("compiling Multipart file to destination.") if err := mp.CompileToFile(filepath.Join(tp.destinationPathRoot, relPath), false); err != nil { return err } diff --git a/internal/tui/interactive.go b/internal/tui/interactive.go index a9f76d2..d576fe3 100644 --- a/internal/tui/interactive.go +++ b/internal/tui/interactive.go @@ -54,7 +54,7 @@ func NewSkaInteractiveService(formTitle string, inputs []configuration.UpstreamC func (s *SkaInteractiveService) ShouldRun() bool { if len(s.formConfig.Inputs) == 0 { - s.log.Info("No inputs in the interactive config.") + s.log.Info("no inputs in the interactive config.") return false } return true @@ -62,7 +62,7 @@ func (s *SkaInteractiveService) ShouldRun() bool { func (s *SkaInteractiveService) Run() error { if !s.ShouldRun() { - s.log.Info("Skipping interactive config") + s.log.Info("skipping interactive config") return nil } s.disableWithLoggingInvalidRegExp() @@ -94,7 +94,7 @@ func (s *SkaInteractiveService) SetDefaults(variables map[string]string) { func (s *SkaInteractiveService) disableWithLoggingInvalidRegExp() { for i := range s.formConfig.Inputs { if _, err := regexp.Compile(s.formConfig.Inputs[i].RegExp); err != nil { - s.log.WithFields(log.Fields{"validation": s.formConfig.Inputs[i].RegExp}).Warnf("RegExp expression is invalid. Error: %s. Ignoring validation.", err) + s.log.WithFields(log.Fields{"validation": s.formConfig.Inputs[i].RegExp}).Warnf("the RegExp expression is invalid. Error: %s. Ignoring validation.", err) s.formConfig.Inputs[i].RegExp = "" } } diff --git a/pkg/skaffolder/create.go b/pkg/skaffolder/create.go index 18349d0..1833742 100644 --- a/pkg/skaffolder/create.go +++ b/pkg/skaffolder/create.go @@ -116,6 +116,6 @@ func (s *SkaCreate) Create() error { return err } - log.WithFields(log.Fields{"method": "Create", "path": s.DestinationPath, "blueprintUri": blueprintProvider.RemoteURI()}).Info("Blueprint expanded under destination path.") + log.WithFields(log.Fields{"method": "Create", "path": s.DestinationPath, "blueprintUri": blueprintProvider.RemoteURI()}).Info("blueprint expanded under destination path.") return nil } diff --git a/pkg/skaffolder/update.go b/pkg/skaffolder/update.go index bbf10b9..580340b 100644 --- a/pkg/skaffolder/update.go +++ b/pkg/skaffolder/update.go @@ -120,6 +120,6 @@ func (s *SkaUpdate) Update() error { return err } - log.WithFields(log.Fields{"method": "Update", "path": s.BaseURI, "blueprintURI": localConfig.BlueprintUpstream()}).Info("Local path updated with blueprint.") + log.WithFields(log.Fields{"method": "Update", "path": s.BaseURI, "blueprintURI": localConfig.BlueprintUpstream()}).Info("local path updated with blueprint.") return nil }