Provisioning: Use Nanogit for basic git operations in Github repository type (#107889)
This commit is contained in:
@@ -58,8 +58,6 @@ require (
|
||||
github.com/fullstorydev/grpchan v1.1.1 // @grafana/grafana-backend-group
|
||||
github.com/gchaincl/sqlhooks v1.3.0 // @grafana/grafana-search-and-storage
|
||||
github.com/getkin/kin-openapi v0.132.0 // @grafana/grafana-app-platform-squad
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // @grafana/grafana-app-platform-squad
|
||||
github.com/go-git/go-git/v5 v5.14.0 // @grafana/grafana-app-platform-squad
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 // @grafana/identity-access-team
|
||||
github.com/go-jose/go-jose/v4 v4.1.0 // indirect; @grafana/identity-access-team
|
||||
github.com/go-kit/log v0.2.1 // @grafana/grafana-backend-group
|
||||
@@ -276,7 +274,6 @@ require (
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/RoaringBitmap/roaring v1.9.3 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
||||
@@ -348,7 +345,6 @@ require (
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dennwc/varint v1.0.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
|
||||
@@ -372,7 +368,6 @@ require (
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/gammazero/deque v0.2.1 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||
@@ -423,7 +418,6 @@ require (
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jaegertracing/jaeger-idl v0.5.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/gofork v1.7.6 // indirect
|
||||
@@ -437,7 +431,6 @@ require (
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect
|
||||
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/asmfmt v1.3.2 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
@@ -492,7 +485,6 @@ require (
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
@@ -519,7 +511,6 @@ require (
|
||||
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
|
||||
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/sony/gobreaker v0.5.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
@@ -538,7 +529,6 @@ require (
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
||||
github.com/yudai/pp v2.0.1+incompatible // indirect
|
||||
@@ -586,7 +576,6 @@ require (
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect
|
||||
gopkg.in/telebot.v3 v3.2.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.33.2 // indirect
|
||||
k8s.io/kms v0.33.2 // indirect
|
||||
modernc.org/libc v1.65.0 // indirect
|
||||
|
||||
@@ -744,7 +744,6 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
|
||||
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
|
||||
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
@@ -803,8 +802,6 @@ github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqR
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
@@ -1054,8 +1051,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
|
||||
github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg=
|
||||
github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc=
|
||||
@@ -1200,8 +1195,6 @@ github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7M
|
||||
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
|
||||
@@ -1211,14 +1204,6 @@ github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3
|
||||
github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
|
||||
github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
|
||||
github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
|
||||
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@@ -1815,8 +1800,6 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE=
|
||||
github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||
@@ -1877,8 +1860,6 @@ github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
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/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
@@ -2175,8 +2156,6 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
@@ -2352,11 +2331,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
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/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
|
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
@@ -2481,8 +2457,6 @@ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+x
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
|
||||
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/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
@@ -3542,8 +3516,6 @@ gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs=
|
||||
gopkg.in/telebot.v3 v3.2.1/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -210,10 +210,6 @@ export interface FeatureToggles {
|
||||
*/
|
||||
provisioning?: boolean;
|
||||
/**
|
||||
* Use experimental git library for provisioning
|
||||
*/
|
||||
nanoGit?: boolean;
|
||||
/**
|
||||
* Start an additional https handler and write kubectl options
|
||||
*/
|
||||
grafanaAPIServerEnsureKubectlAccess?: boolean;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package export
|
||||
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package export
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockWrapWithCloneFn is an autogenerated mock type for the WrapWithCloneFn type
|
||||
type MockWrapWithCloneFn struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockWrapWithCloneFn_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockWrapWithCloneFn) EXPECT() *MockWrapWithCloneFn_Expecter {
|
||||
return &MockWrapWithCloneFn_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Execute provides a mock function with given fields: ctx, repo, cloneOptions, pushOptions, fn
|
||||
func (_m *MockWrapWithCloneFn) Execute(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
ret := _m.Called(ctx, repo, cloneOptions, pushOptions, fn)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Execute")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, repository.Repository, repository.CloneOptions, repository.PushOptions, func(repository.Repository, bool) error) error); ok {
|
||||
r0 = rf(ctx, repo, cloneOptions, pushOptions, fn)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockWrapWithCloneFn_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute'
|
||||
type MockWrapWithCloneFn_Execute_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Execute is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - repo repository.Repository
|
||||
// - cloneOptions repository.CloneOptions
|
||||
// - pushOptions repository.PushOptions
|
||||
// - fn func(repository.Repository , bool) error
|
||||
func (_e *MockWrapWithCloneFn_Expecter) Execute(ctx interface{}, repo interface{}, cloneOptions interface{}, pushOptions interface{}, fn interface{}) *MockWrapWithCloneFn_Execute_Call {
|
||||
return &MockWrapWithCloneFn_Execute_Call{Call: _e.mock.On("Execute", ctx, repo, cloneOptions, pushOptions, fn)}
|
||||
}
|
||||
|
||||
func (_c *MockWrapWithCloneFn_Execute_Call) Run(run func(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repository.Repository, bool) error)) *MockWrapWithCloneFn_Execute_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(repository.Repository), args[2].(repository.CloneOptions), args[3].(repository.PushOptions), args[4].(func(repository.Repository, bool) error))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWrapWithCloneFn_Execute_Call) Return(_a0 error) *MockWrapWithCloneFn_Execute_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWrapWithCloneFn_Execute_Call) RunAndReturn(run func(context.Context, repository.Repository, repository.CloneOptions, repository.PushOptions, func(repository.Repository, bool) error) error) *MockWrapWithCloneFn_Execute_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockWrapWithCloneFn creates a new instance of MockWrapWithCloneFn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockWrapWithCloneFn(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockWrapWithCloneFn {
|
||||
mock := &MockWrapWithCloneFn{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package export
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockWrapWithStageFn is an autogenerated mock type for the WrapWithStageFn type
|
||||
type MockWrapWithStageFn struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockWrapWithStageFn_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockWrapWithStageFn) EXPECT() *MockWrapWithStageFn_Expecter {
|
||||
return &MockWrapWithStageFn_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Execute provides a mock function with given fields: ctx, repo, stageOptions, fn
|
||||
func (_m *MockWrapWithStageFn) Execute(ctx context.Context, repo repository.Repository, stageOptions repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
ret := _m.Called(ctx, repo, stageOptions, fn)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Execute")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, repository.Repository, repository.StageOptions, func(repository.Repository, bool) error) error); ok {
|
||||
r0 = rf(ctx, repo, stageOptions, fn)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockWrapWithStageFn_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute'
|
||||
type MockWrapWithStageFn_Execute_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Execute is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - repo repository.Repository
|
||||
// - stageOptions repository.StageOptions
|
||||
// - fn func(repository.Repository , bool) error
|
||||
func (_e *MockWrapWithStageFn_Expecter) Execute(ctx interface{}, repo interface{}, stageOptions interface{}, fn interface{}) *MockWrapWithStageFn_Execute_Call {
|
||||
return &MockWrapWithStageFn_Execute_Call{Call: _e.mock.On("Execute", ctx, repo, stageOptions, fn)}
|
||||
}
|
||||
|
||||
func (_c *MockWrapWithStageFn_Execute_Call) Run(run func(ctx context.Context, repo repository.Repository, stageOptions repository.StageOptions, fn func(repository.Repository, bool) error)) *MockWrapWithStageFn_Execute_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(repository.Repository), args[2].(repository.StageOptions), args[3].(func(repository.Repository, bool) error))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWrapWithStageFn_Execute_Call) Return(_a0 error) *MockWrapWithStageFn_Execute_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWrapWithStageFn_Execute_Call) RunAndReturn(run func(context.Context, repository.Repository, repository.StageOptions, func(repository.Repository, bool) error) error) *MockWrapWithStageFn_Execute_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockWrapWithStageFn creates a new instance of MockWrapWithStageFn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockWrapWithStageFn(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockWrapWithStageFn {
|
||||
mock := &MockWrapWithStageFn{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -67,7 +67,7 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions,
|
||||
}
|
||||
}
|
||||
|
||||
if err := exportResource(ctx, options, client, shim, repositoryResources, progress); err != nil {
|
||||
if err := exportResource(ctx, kind.Resource, options, client, shim, repositoryResources, progress); err != nil {
|
||||
return fmt.Errorf("export %s: %w", kind.Resource, err)
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,7 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions,
|
||||
}
|
||||
|
||||
func exportResource(ctx context.Context,
|
||||
resource string,
|
||||
options provisioning.ExportJobOptions,
|
||||
client dynamic.ResourceInterface,
|
||||
shim conversionShim,
|
||||
@@ -88,7 +89,7 @@ func exportResource(ctx context.Context,
|
||||
gvk := item.GroupVersionKind()
|
||||
result := jobs.JobResourceResult{
|
||||
Name: item.GetName(),
|
||||
Resource: gvk.Kind,
|
||||
Resource: resource,
|
||||
Group: gvk.Group,
|
||||
Action: repository.FileActionCreated,
|
||||
}
|
||||
|
||||
@@ -9,34 +9,33 @@ import (
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
gogit "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/go-git"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
|
||||
)
|
||||
|
||||
//go:generate mockery --name ExportFn --structname MockExportFn --inpackage --filename mock_export_fn.go --with-expecter
|
||||
type ExportFn func(ctx context.Context, repoName string, options provisioning.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error
|
||||
|
||||
//go:generate mockery --name WrapWithCloneFn --structname MockWrapWithCloneFn --inpackage --filename mock_wrap_with_clone_fn.go --with-expecter
|
||||
type WrapWithCloneFn func(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repo repository.Repository, cloned bool) error) error
|
||||
//go:generate mockery --name WrapWithStageFn --structname MockWrapWithStageFn --inpackage --filename mock_wrap_with_stage_fn.go --with-expecter
|
||||
type WrapWithStageFn func(ctx context.Context, repo repository.Repository, stageOptions repository.StageOptions, fn func(repo repository.Repository, staged bool) error) error
|
||||
|
||||
type ExportWorker struct {
|
||||
clientFactory resources.ClientFactory
|
||||
repositoryResources resources.RepositoryResourcesFactory
|
||||
exportFn ExportFn
|
||||
wrapWithCloneFn WrapWithCloneFn
|
||||
wrapWithStageFn WrapWithStageFn
|
||||
}
|
||||
|
||||
func NewExportWorker(
|
||||
clientFactory resources.ClientFactory,
|
||||
repositoryResources resources.RepositoryResourcesFactory,
|
||||
exportFn ExportFn,
|
||||
wrapWithCloneFn WrapWithCloneFn,
|
||||
wrapWithStageFn WrapWithStageFn,
|
||||
) *ExportWorker {
|
||||
return &ExportWorker{
|
||||
clientFactory: clientFactory,
|
||||
repositoryResources: repositoryResources,
|
||||
exportFn: exportFn,
|
||||
wrapWithCloneFn: wrapWithCloneFn,
|
||||
wrapWithStageFn: wrapWithStageFn,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,32 +56,9 @@ func (r *ExportWorker) Process(ctx context.Context, repo repository.Repository,
|
||||
return err
|
||||
}
|
||||
|
||||
writer := gogit.Progress(func(line string) {
|
||||
progress.SetMessage(ctx, line)
|
||||
}, "finished")
|
||||
|
||||
cloneOptions := repository.CloneOptions{
|
||||
cloneOptions := repository.StageOptions{
|
||||
Timeout: 10 * time.Minute,
|
||||
PushOnWrites: false,
|
||||
Progress: writer,
|
||||
BeforeFn: func() error {
|
||||
progress.SetMessage(ctx, "clone target")
|
||||
// :( the branch is now baked into the repo
|
||||
if options.Branch != "" {
|
||||
return fmt.Errorf("branch is not supported for clonable repositories")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
pushOptions := repository.PushOptions{
|
||||
Timeout: 10 * time.Minute,
|
||||
Progress: writer,
|
||||
BeforeFn: func() error {
|
||||
progress.SetMessage(ctx, "push changes")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
fn := func(repo repository.Repository, _ bool) error {
|
||||
@@ -104,5 +80,5 @@ func (r *ExportWorker) Process(ctx context.Context, repo repository.Repository,
|
||||
return r.exportFn(ctx, cfg.Name, *options, clients, repositoryResources, progress)
|
||||
}
|
||||
|
||||
return r.wrapWithCloneFn(ctx, repo, cloneOptions, pushOptions, fn)
|
||||
return r.wrapWithStageFn(ctx, repo, cloneOptions, fn)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -143,12 +142,12 @@ func TestExportWorker_ProcessFailedToCreateClients(t *testing.T) {
|
||||
mockClients := resources.NewMockClientFactory(t)
|
||||
|
||||
mockClients.On("Clients", context.Background(), "test-namespace").Return(nil, errors.New("failed to create clients"))
|
||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
||||
mockCloneFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
mockStageFn := NewMockWrapWithStageFn(t)
|
||||
mockStageFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return fn(repo, true)
|
||||
})
|
||||
|
||||
r := NewExportWorker(mockClients, nil, nil, mockCloneFn.Execute)
|
||||
r := NewExportWorker(mockClients, nil, nil, mockStageFn.Execute)
|
||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||
|
||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||
@@ -179,12 +178,12 @@ func TestExportWorker_ProcessNotReaderWriter(t *testing.T) {
|
||||
mockClients.On("Clients", context.Background(), "test-namespace").Return(resourceClients, nil)
|
||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||
|
||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
||||
mockCloneFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
mockStageFn := NewMockWrapWithStageFn(t)
|
||||
mockStageFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return fn(repo, true)
|
||||
})
|
||||
|
||||
r := NewExportWorker(mockClients, nil, nil, mockCloneFn.Execute)
|
||||
r := NewExportWorker(mockClients, nil, nil, mockStageFn.Execute)
|
||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||
require.EqualError(t, err, "export job submitted targeting repository that is not a ReaderWriter")
|
||||
}
|
||||
@@ -216,16 +215,16 @@ func TestExportWorker_ProcessRepositoryResourcesError(t *testing.T) {
|
||||
mockRepoResources.On("Client", context.Background(), mockRepo).Return(nil, fmt.Errorf("failed to create repository resources client"))
|
||||
|
||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
||||
mockCloneFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
mockStageFn := NewMockWrapWithStageFn(t)
|
||||
mockStageFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return fn(repo, true)
|
||||
})
|
||||
r := NewExportWorker(mockClients, mockRepoResources, nil, mockCloneFn.Execute)
|
||||
r := NewExportWorker(mockClients, mockRepoResources, nil, mockStageFn.Execute)
|
||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||
require.EqualError(t, err, "create repository resource client: failed to create repository resources client")
|
||||
}
|
||||
|
||||
func TestExportWorker_ProcessCloneAndPushOptions(t *testing.T) {
|
||||
func TestExportWorker_ProcessStageOptions(t *testing.T) {
|
||||
job := v0alpha1.Job{
|
||||
Spec: v0alpha1.JobSpec{
|
||||
Action: v0alpha1.JobActionPush,
|
||||
@@ -245,9 +244,7 @@ func TestExportWorker_ProcessCloneAndPushOptions(t *testing.T) {
|
||||
})
|
||||
|
||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||
// Verify progress messages are set
|
||||
mockProgress.On("SetMessage", mock.Anything, "clone target").Return()
|
||||
mockProgress.On("SetMessage", mock.Anything, "push changes").Return()
|
||||
// No progress messages expected in current implementation
|
||||
|
||||
mockClients := resources.NewMockClientFactory(t)
|
||||
mockResourceClients := resources.NewMockResourceClients(t)
|
||||
@@ -260,21 +257,15 @@ func TestExportWorker_ProcessCloneAndPushOptions(t *testing.T) {
|
||||
mockExportFn := NewMockExportFn(t)
|
||||
mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
||||
mockStageFn := NewMockWrapWithStageFn(t)
|
||||
// Verify clone and push options
|
||||
mockCloneFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.CloneOptions) bool {
|
||||
return opts.Timeout == 10*time.Minute && !opts.PushOnWrites && opts.BeforeFn != nil
|
||||
}), mock.MatchedBy(func(opts repository.PushOptions) bool {
|
||||
return opts.Timeout == 10*time.Minute && opts.Progress != nil && opts.BeforeFn != nil
|
||||
}), mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
// Execute both BeforeFn functions to verify progress messages
|
||||
assert.NoError(t, cloneOpts.BeforeFn())
|
||||
assert.NoError(t, pushOpts.BeforeFn())
|
||||
|
||||
mockStageFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.StageOptions) bool {
|
||||
return opts.Timeout == 10*time.Minute && !opts.PushOnWrites
|
||||
}), mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return fn(repo, true)
|
||||
})
|
||||
|
||||
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockCloneFn.Execute)
|
||||
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockStageFn.Execute)
|
||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -310,17 +301,17 @@ func TestExportWorker_ProcessExportFnError(t *testing.T) {
|
||||
mockExportFn := NewMockExportFn(t)
|
||||
mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(errors.New("export failed"))
|
||||
|
||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
||||
mockCloneFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
mockStageFn := NewMockWrapWithStageFn(t)
|
||||
mockStageFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return fn(repo, true)
|
||||
})
|
||||
|
||||
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockCloneFn.Execute)
|
||||
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockStageFn.Execute)
|
||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||
require.EqualError(t, err, "export failed")
|
||||
}
|
||||
|
||||
func TestExportWorker_ProcessWrapWithCloneFnError(t *testing.T) {
|
||||
func TestExportWorker_ProcessWrapWithStageFnError(t *testing.T) {
|
||||
job := v0alpha1.Job{
|
||||
Spec: v0alpha1.JobSpec{
|
||||
Action: v0alpha1.JobActionPush,
|
||||
@@ -340,15 +331,15 @@ func TestExportWorker_ProcessWrapWithCloneFnError(t *testing.T) {
|
||||
})
|
||||
|
||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
||||
mockCloneFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(errors.New("clone failed"))
|
||||
mockStageFn := NewMockWrapWithStageFn(t)
|
||||
mockStageFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything).Return(errors.New("stage failed"))
|
||||
|
||||
r := NewExportWorker(nil, nil, nil, mockCloneFn.Execute)
|
||||
r := NewExportWorker(nil, nil, nil, mockStageFn.Execute)
|
||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||
require.EqualError(t, err, "clone failed")
|
||||
require.EqualError(t, err, "stage failed")
|
||||
}
|
||||
|
||||
func TestExportWorker_ProcessBranchNotAllowedForClonableRepositories(t *testing.T) {
|
||||
func TestExportWorker_ProcessBranchNotAllowedForStageableRepositories(t *testing.T) {
|
||||
job := v0alpha1.Job{
|
||||
Spec: v0alpha1.JobSpec{
|
||||
Action: v0alpha1.JobActionPush,
|
||||
@@ -362,24 +353,16 @@ func TestExportWorker_ProcessBranchNotAllowedForClonableRepositories(t *testing.
|
||||
mockRepo.On("Config").Return(&v0alpha1.Repository{
|
||||
Spec: v0alpha1.RepositorySpec{
|
||||
Type: v0alpha1.GitHubRepositoryType,
|
||||
Workflows: []v0alpha1.Workflow{v0alpha1.BranchWorkflow},
|
||||
Workflows: []v0alpha1.Workflow{v0alpha1.WriteWorkflow}, // Only write workflow, not branch
|
||||
},
|
||||
})
|
||||
|
||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||
mockProgress.On("SetMessage", mock.Anything, "clone target").Return()
|
||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
||||
mockCloneFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
if cloneOpts.BeforeFn != nil {
|
||||
return cloneOpts.BeforeFn()
|
||||
}
|
||||
// No progress messages expected in current implementation
|
||||
|
||||
return fn(repo, true)
|
||||
})
|
||||
|
||||
r := NewExportWorker(nil, nil, nil, mockCloneFn.Execute)
|
||||
r := NewExportWorker(nil, nil, nil, nil)
|
||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||
require.EqualError(t, err, "branch is not supported for clonable repositories")
|
||||
require.EqualError(t, err, "this repository does not support the branch workflow")
|
||||
}
|
||||
|
||||
func TestExportWorker_ProcessGitRepository(t *testing.T) {
|
||||
@@ -407,9 +390,7 @@ func TestExportWorker_ProcessGitRepository(t *testing.T) {
|
||||
})
|
||||
|
||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||
// Verify progress messages are set
|
||||
mockProgress.On("SetMessage", mock.Anything, "clone target").Return()
|
||||
mockProgress.On("SetMessage", mock.Anything, "push changes").Return()
|
||||
// No progress messages expected in current implementation
|
||||
|
||||
mockClients := resources.NewMockClientFactory(t)
|
||||
mockResourceClients := resources.NewMockResourceClients(t)
|
||||
@@ -422,21 +403,15 @@ func TestExportWorker_ProcessGitRepository(t *testing.T) {
|
||||
mockExportFn := NewMockExportFn(t)
|
||||
mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
||||
mockStageFn := NewMockWrapWithStageFn(t)
|
||||
// Verify clone and push options
|
||||
mockCloneFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.CloneOptions) bool {
|
||||
return opts.Timeout == 10*time.Minute && !opts.PushOnWrites && opts.BeforeFn != nil
|
||||
}), mock.MatchedBy(func(opts repository.PushOptions) bool {
|
||||
return opts.Timeout == 10*time.Minute && opts.Progress != nil && opts.BeforeFn != nil
|
||||
}), mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
// Execute both BeforeFn functions to verify progress messages
|
||||
assert.NoError(t, cloneOpts.BeforeFn())
|
||||
assert.NoError(t, pushOpts.BeforeFn())
|
||||
|
||||
mockStageFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.StageOptions) bool {
|
||||
return opts.Timeout == 10*time.Minute && !opts.PushOnWrites
|
||||
}), mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return fn(repo, true)
|
||||
})
|
||||
|
||||
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockCloneFn.Execute)
|
||||
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockStageFn.Execute)
|
||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -477,12 +452,12 @@ func TestExportWorker_ProcessGitRepositoryExportFnError(t *testing.T) {
|
||||
mockExportFn := NewMockExportFn(t)
|
||||
mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(errors.New("export failed"))
|
||||
|
||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
||||
mockCloneFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
mockStageFn := NewMockWrapWithStageFn(t)
|
||||
mockStageFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return fn(repo, true)
|
||||
})
|
||||
|
||||
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockCloneFn.Execute)
|
||||
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockStageFn.Execute)
|
||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||
require.EqualError(t, err, "export failed")
|
||||
}
|
||||
|
||||
@@ -10,57 +10,38 @@ import (
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
gogit "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/go-git"
|
||||
)
|
||||
|
||||
type LegacyMigrator struct {
|
||||
legacyMigrator LegacyResourcesMigrator
|
||||
storageSwapper StorageSwapper
|
||||
syncWorker jobs.Worker
|
||||
wrapWithCloneFn WrapWithCloneFn
|
||||
wrapWithStageFn WrapWithStageFn
|
||||
}
|
||||
|
||||
func NewLegacyMigrator(
|
||||
legacyMigrator LegacyResourcesMigrator,
|
||||
storageSwapper StorageSwapper,
|
||||
syncWorker jobs.Worker,
|
||||
wrapWithCloneFn WrapWithCloneFn,
|
||||
wrapWithStageFn WrapWithStageFn,
|
||||
) *LegacyMigrator {
|
||||
return &LegacyMigrator{
|
||||
legacyMigrator: legacyMigrator,
|
||||
storageSwapper: storageSwapper,
|
||||
syncWorker: syncWorker,
|
||||
wrapWithCloneFn: wrapWithCloneFn,
|
||||
wrapWithStageFn: wrapWithStageFn,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *LegacyMigrator) Migrate(ctx context.Context, rw repository.ReaderWriter, options provisioning.MigrateJobOptions, progress jobs.JobProgressRecorder) error {
|
||||
namespace := rw.Config().Namespace
|
||||
|
||||
writer := gogit.Progress(func(line string) {
|
||||
progress.SetMessage(ctx, line)
|
||||
}, "finished")
|
||||
cloneOptions := repository.CloneOptions{
|
||||
stageOptions := repository.StageOptions{
|
||||
PushOnWrites: options.History,
|
||||
// TODO: make this configurable
|
||||
Timeout: 10 * time.Minute,
|
||||
Progress: writer,
|
||||
BeforeFn: func() error {
|
||||
progress.SetMessage(ctx, "clone repository")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
pushOptions := repository.PushOptions{
|
||||
// TODO: make this configurable
|
||||
Timeout: 10 * time.Minute,
|
||||
Progress: writer,
|
||||
BeforeFn: func() error {
|
||||
progress.SetMessage(ctx, "push changes")
|
||||
return nil
|
||||
},
|
||||
Timeout: 10 * time.Minute,
|
||||
}
|
||||
|
||||
if err := m.wrapWithCloneFn(ctx, rw, cloneOptions, pushOptions, func(repo repository.Repository, cloned bool) error {
|
||||
if err := m.wrapWithStageFn(ctx, rw, stageOptions, func(repo repository.Repository, staged bool) error {
|
||||
rw, ok := repo.(repository.ReaderWriter)
|
||||
if !ok {
|
||||
return errors.New("migration job submitted targeting repository that is not a ReaderWriter")
|
||||
|
||||
@@ -3,7 +3,6 @@ package migrate
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -16,12 +15,12 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
)
|
||||
|
||||
func TestWrapWithCloneFn(t *testing.T) {
|
||||
func TestWrapWithStageFn(t *testing.T) {
|
||||
t.Run("should return error when repository is not a ReaderWriter", func(t *testing.T) {
|
||||
// Setup
|
||||
ctx := context.Background()
|
||||
// Create the wrapper function that matches WrapWithCloneFn signature
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
// pass a reader to function call
|
||||
repo := repository.NewMockReader(t)
|
||||
return fn(repo, true)
|
||||
@@ -56,7 +55,7 @@ func TestWrapWithCloneFn_Error(t *testing.T) {
|
||||
expectedErr := errors.New("clone failed")
|
||||
|
||||
// Create the wrapper function that returns an error
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return expectedErr
|
||||
}
|
||||
|
||||
@@ -98,7 +97,7 @@ func TestLegacyMigrator_MigrateFails(t *testing.T) {
|
||||
mockWorker := jobs.NewMockWorker(t)
|
||||
|
||||
// Create a wrapper function that calls the provided function
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return fn(rw, true)
|
||||
}
|
||||
|
||||
@@ -147,7 +146,7 @@ func TestLegacyMigrator_ResetUnifiedStorageFails(t *testing.T) {
|
||||
mockWorker := jobs.NewMockWorker(t)
|
||||
|
||||
// Create a wrapper function that calls the provided function
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return fn(rw, true)
|
||||
}
|
||||
|
||||
@@ -202,7 +201,7 @@ func TestLegacyMigrator_SyncFails(t *testing.T) {
|
||||
}), mock.Anything).Return(expectedErr)
|
||||
|
||||
// Create a wrapper function that calls the provided function
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return fn(rw, true)
|
||||
}
|
||||
|
||||
@@ -257,7 +256,7 @@ func TestLegacyMigrator_SyncFails(t *testing.T) {
|
||||
}), mock.Anything).Return(syncErr)
|
||||
|
||||
// Create a wrapper function that calls the provided function
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return fn(rw, true)
|
||||
}
|
||||
|
||||
@@ -310,7 +309,7 @@ func TestLegacyMigrator_Success(t *testing.T) {
|
||||
}), mock.Anything).Return(nil)
|
||||
|
||||
// Create a wrapper function that calls the provided function
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return fn(rw, true)
|
||||
}
|
||||
|
||||
@@ -352,19 +351,7 @@ func TestLegacyMigrator_BeforeFnExecution(t *testing.T) {
|
||||
mockStorageSwapper := NewMockStorageSwapper(t)
|
||||
mockWorker := jobs.NewMockWorker(t)
|
||||
// Create a wrapper function that calls the provided function
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
if clone.BeforeFn != nil {
|
||||
if err := clone.BeforeFn(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if push.BeforeFn != nil {
|
||||
if err := push.BeforeFn(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return errors.New("abort test here")
|
||||
}
|
||||
|
||||
@@ -376,8 +363,7 @@ func TestLegacyMigrator_BeforeFnExecution(t *testing.T) {
|
||||
)
|
||||
|
||||
progress := jobs.NewMockJobProgressRecorder(t)
|
||||
progress.On("SetMessage", mock.Anything, "clone repository").Return()
|
||||
progress.On("SetMessage", mock.Anything, "push changes").Return()
|
||||
// No progress messages expected in current staging implementation
|
||||
|
||||
// Execute
|
||||
repo := repository.NewMockRepository(t)
|
||||
@@ -399,19 +385,7 @@ func TestLegacyMigrator_ProgressScanner(t *testing.T) {
|
||||
mockWorker := jobs.NewMockWorker(t)
|
||||
|
||||
// Create a wrapper function that calls the provided function
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
if clone.Progress != nil {
|
||||
if _, err := clone.Progress.Write([]byte("clone repository\n")); err != nil {
|
||||
return fmt.Errorf("failed to write to clone progress in tests: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if push.Progress != nil {
|
||||
if _, err := push.Progress.Write([]byte("push changes\n")); err != nil {
|
||||
return fmt.Errorf("failed to write to push progress in tests: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||
return errors.New("abort test here")
|
||||
}
|
||||
|
||||
@@ -423,8 +397,7 @@ func TestLegacyMigrator_ProgressScanner(t *testing.T) {
|
||||
)
|
||||
|
||||
progress := jobs.NewMockJobProgressRecorder(t)
|
||||
progress.On("SetMessage", mock.Anything, "clone repository").Return()
|
||||
progress.On("SetMessage", mock.Anything, "push changes").Return()
|
||||
// No progress messages expected in current staging implementation
|
||||
|
||||
repo := repository.NewMockRepository(t)
|
||||
repo.On("Config").Return(&provisioning.Repository{
|
||||
@@ -437,10 +410,7 @@ func TestLegacyMigrator_ProgressScanner(t *testing.T) {
|
||||
require.EqualError(t, err, "migrate from SQL: abort test here")
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
if len(progress.Calls) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
// No progress message calls expected in current staging implementation
|
||||
return progress.AssertExpectations(t)
|
||||
}, time.Second, 10*time.Millisecond)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package migrate
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package migrate
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package migrate
|
||||
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package migrate
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockWrapWithCloneFn is an autogenerated mock type for the WrapWithCloneFn type
|
||||
type MockWrapWithCloneFn struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockWrapWithCloneFn_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockWrapWithCloneFn) EXPECT() *MockWrapWithCloneFn_Expecter {
|
||||
return &MockWrapWithCloneFn_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Execute provides a mock function with given fields: ctx, repo, cloneOptions, pushOptions, fn
|
||||
func (_m *MockWrapWithCloneFn) Execute(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
||||
ret := _m.Called(ctx, repo, cloneOptions, pushOptions, fn)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Execute")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, repository.Repository, repository.CloneOptions, repository.PushOptions, func(repository.Repository, bool) error) error); ok {
|
||||
r0 = rf(ctx, repo, cloneOptions, pushOptions, fn)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockWrapWithCloneFn_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute'
|
||||
type MockWrapWithCloneFn_Execute_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Execute is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - repo repository.Repository
|
||||
// - cloneOptions repository.CloneOptions
|
||||
// - pushOptions repository.PushOptions
|
||||
// - fn func(repository.Repository , bool) error
|
||||
func (_e *MockWrapWithCloneFn_Expecter) Execute(ctx interface{}, repo interface{}, cloneOptions interface{}, pushOptions interface{}, fn interface{}) *MockWrapWithCloneFn_Execute_Call {
|
||||
return &MockWrapWithCloneFn_Execute_Call{Call: _e.mock.On("Execute", ctx, repo, cloneOptions, pushOptions, fn)}
|
||||
}
|
||||
|
||||
func (_c *MockWrapWithCloneFn_Execute_Call) Run(run func(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repository.Repository, bool) error)) *MockWrapWithCloneFn_Execute_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(repository.Repository), args[2].(repository.CloneOptions), args[3].(repository.PushOptions), args[4].(func(repository.Repository, bool) error))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWrapWithCloneFn_Execute_Call) Return(_a0 error) *MockWrapWithCloneFn_Execute_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWrapWithCloneFn_Execute_Call) RunAndReturn(run func(context.Context, repository.Repository, repository.CloneOptions, repository.PushOptions, func(repository.Repository, bool) error) error) *MockWrapWithCloneFn_Execute_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockWrapWithCloneFn creates a new instance of MockWrapWithCloneFn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockWrapWithCloneFn(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockWrapWithCloneFn {
|
||||
mock := &MockWrapWithCloneFn{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
)
|
||||
|
||||
//go:generate mockery --name WrapWithCloneFn --structname MockWrapWithCloneFn --inpackage --filename mock_wrap_with_clone_fn.go --with-expecter
|
||||
type WrapWithCloneFn func(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repo repository.Repository, cloned bool) error) error
|
||||
//go:generate mockery --name WrapWithStageFn --structname MockWrapWithStageFn --inpackage --filename mock_wrap_with_stage_fn.go --with-expecter
|
||||
type WrapWithStageFn func(ctx context.Context, repo repository.Repository, stageOptions repository.StageOptions, fn func(repo repository.Repository, staged bool) error) error
|
||||
|
||||
type UnifiedStorageMigrator struct {
|
||||
namespaceCleaner NamespaceCleaner
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/local"
|
||||
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
|
||||
)
|
||||
|
||||
@@ -88,7 +89,7 @@ func TestMigrationWorker_WithHistory(t *testing.T) {
|
||||
progressRecorder.On("SetTotal", mock.Anything, 10).Return()
|
||||
progressRecorder.On("Strict").Return()
|
||||
|
||||
repo := repository.NewLocal(&provisioning.Repository{}, nil)
|
||||
repo := local.NewLocal(&provisioning.Repository{}, nil)
|
||||
err := worker.Process(context.Background(), repo, job, progressRecorder)
|
||||
require.EqualError(t, err, "history is only supported for github repositories")
|
||||
})
|
||||
|
||||
@@ -45,9 +45,9 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/migrate"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/sync"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/git"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
gogit "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/go-git"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/nanogit"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/local"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources/signature"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||
@@ -76,7 +76,7 @@ type APIBuilder struct {
|
||||
features featuremgmt.FeatureToggles
|
||||
|
||||
getter rest.Getter
|
||||
localFileResolver *repository.LocalFolderResolver
|
||||
localFileResolver *local.LocalFolderResolver
|
||||
parsers resources.ParserFactory
|
||||
repositoryResources resources.RepositoryResourcesFactory
|
||||
clients resources.ClientFactory
|
||||
@@ -105,7 +105,7 @@ type APIBuilder struct {
|
||||
// It avoids anything that is core to Grafana, such that it can be used in a multi-tenant service down the line.
|
||||
// This means there are no hidden dependencies, and no use of e.g. *settings.Cfg.
|
||||
func NewAPIBuilder(
|
||||
local *repository.LocalFolderResolver,
|
||||
local *local.LocalFolderResolver,
|
||||
features featuremgmt.FeatureToggles,
|
||||
unified resource.ResourceClient,
|
||||
clonedir string, // where repo clones are managed
|
||||
@@ -168,14 +168,7 @@ func RegisterAPIService(
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
logger := logging.DefaultLogger.With("logger", "provisioning startup")
|
||||
if features.IsEnabledGlobally(featuremgmt.FlagNanoGit) {
|
||||
logger.Info("Using nanogit for repositories")
|
||||
} else {
|
||||
logger.Debug("Using go-git and Github API for repositories")
|
||||
}
|
||||
|
||||
folderResolver := &repository.LocalFolderResolver{
|
||||
folderResolver := &local.LocalFolderResolver{
|
||||
PermittedPrefixes: cfg.PermittedProvisioningPaths,
|
||||
HomePath: safepath.Clean(cfg.HomePath),
|
||||
}
|
||||
@@ -606,11 +599,12 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH
|
||||
|
||||
b.repositoryLister = repoInformer.Lister()
|
||||
|
||||
stageIfPossible := repository.WrapWithStageAndPushIfPossible
|
||||
exportWorker := export.NewExportWorker(
|
||||
b.clients,
|
||||
b.repositoryResources,
|
||||
export.ExportAll,
|
||||
repository.WrapWithCloneAndPushIfPossible,
|
||||
stageIfPossible,
|
||||
)
|
||||
|
||||
b.statusPatcher = controller.NewRepositoryStatusPatcher(b.GetClient())
|
||||
@@ -636,7 +630,7 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH
|
||||
legacyResources,
|
||||
storageSwapper,
|
||||
syncWorker,
|
||||
repository.WrapWithCloneAndPushIfPossible,
|
||||
stageIfPossible,
|
||||
)
|
||||
|
||||
cleaner := migrate.NewNamespaceCleaner(b.clients)
|
||||
@@ -1170,52 +1164,63 @@ func (b *APIBuilder) AsRepository(ctx context.Context, r *provisioning.Repositor
|
||||
|
||||
switch r.Spec.Type {
|
||||
case provisioning.LocalRepositoryType:
|
||||
return repository.NewLocal(r, b.localFileResolver), nil
|
||||
return local.NewLocal(r, b.localFileResolver), nil
|
||||
case provisioning.GitRepositoryType:
|
||||
return nanogit.NewGitRepository(ctx, b.secrets, r, nanogit.RepositoryConfig{
|
||||
// Decrypt token if needed
|
||||
token := r.Spec.Git.Token
|
||||
if token == "" {
|
||||
decrypted, err := b.secrets.Decrypt(ctx, r.Spec.Git.EncryptedToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt git token: %w", err)
|
||||
}
|
||||
token = string(decrypted)
|
||||
}
|
||||
|
||||
return git.NewGitRepository(ctx, r, git.RepositoryConfig{
|
||||
URL: r.Spec.Git.URL,
|
||||
Branch: r.Spec.Git.Branch,
|
||||
Path: r.Spec.Git.Path,
|
||||
Token: r.Spec.Git.Token,
|
||||
Token: token,
|
||||
EncryptedToken: r.Spec.Git.EncryptedToken,
|
||||
})
|
||||
case provisioning.GitHubRepositoryType:
|
||||
cloneFn := func(ctx context.Context, opts repository.CloneOptions) (repository.ClonedRepository, error) {
|
||||
return gogit.Clone(ctx, b.clonedir, r, opts, b.secrets)
|
||||
}
|
||||
|
||||
apiRepo, err := repository.NewGitHub(ctx, r, b.ghFactory, b.secrets, cloneFn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create github API repository: %w", err)
|
||||
}
|
||||
|
||||
logger := logging.FromContext(ctx).With("url", r.Spec.GitHub.URL, "branch", r.Spec.GitHub.Branch, "path", r.Spec.GitHub.Path)
|
||||
if !b.features.IsEnabledGlobally(featuremgmt.FlagNanoGit) {
|
||||
logger.Debug("Instantiating Github repository with go-git and Github API")
|
||||
return apiRepo, nil
|
||||
}
|
||||
|
||||
logger.Info("Instantiating Github repository with nanogit")
|
||||
logger.Info("Instantiating Github repository")
|
||||
|
||||
ghCfg := r.Spec.GitHub
|
||||
if ghCfg == nil {
|
||||
return nil, fmt.Errorf("github configuration is required for nano git")
|
||||
}
|
||||
|
||||
gitCfg := nanogit.RepositoryConfig{
|
||||
// Decrypt GitHub token if needed
|
||||
ghToken := ghCfg.Token
|
||||
if ghToken == "" && len(ghCfg.EncryptedToken) > 0 {
|
||||
decrypted, err := b.secrets.Decrypt(ctx, ghCfg.EncryptedToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt github token: %w", err)
|
||||
}
|
||||
ghToken = string(decrypted)
|
||||
}
|
||||
|
||||
gitCfg := git.RepositoryConfig{
|
||||
URL: ghCfg.URL,
|
||||
Branch: ghCfg.Branch,
|
||||
Path: ghCfg.Path,
|
||||
Token: ghCfg.Token,
|
||||
Token: ghToken,
|
||||
EncryptedToken: ghCfg.EncryptedToken,
|
||||
}
|
||||
|
||||
nanogitRepo, err := nanogit.NewGitRepository(ctx, b.secrets, r, gitCfg)
|
||||
gitRepo, err := git.NewGitRepository(ctx, r, gitCfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating nanogit repository: %w", err)
|
||||
return nil, fmt.Errorf("error creating git repository: %w", err)
|
||||
}
|
||||
|
||||
return nanogit.NewGithubRepository(apiRepo, nanogitRepo), nil
|
||||
ghRepo, err := github.NewGitHub(ctx, r, gitRepo, b.ghFactory, ghToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating github repository: %w", err)
|
||||
}
|
||||
|
||||
return ghRepo, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown repository type (%s)", r.Spec.Type)
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockClonableRepository is an autogenerated mock type for the ClonableRepository type
|
||||
type MockClonableRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockClonableRepository_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockClonableRepository) EXPECT() *MockClonableRepository_Expecter {
|
||||
return &MockClonableRepository_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Clone provides a mock function with given fields: ctx, opts
|
||||
func (_m *MockClonableRepository) Clone(ctx context.Context, opts CloneOptions) (ClonedRepository, error) {
|
||||
ret := _m.Called(ctx, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Clone")
|
||||
}
|
||||
|
||||
var r0 ClonedRepository
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) (ClonedRepository, error)); ok {
|
||||
return rf(ctx, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) ClonedRepository); ok {
|
||||
r0 = rf(ctx, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(ClonedRepository)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, CloneOptions) error); ok {
|
||||
r1 = rf(ctx, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClonableRepository_Clone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clone'
|
||||
type MockClonableRepository_Clone_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Clone is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - opts CloneOptions
|
||||
func (_e *MockClonableRepository_Expecter) Clone(ctx interface{}, opts interface{}) *MockClonableRepository_Clone_Call {
|
||||
return &MockClonableRepository_Clone_Call{Call: _e.mock.On("Clone", ctx, opts)}
|
||||
}
|
||||
|
||||
func (_c *MockClonableRepository_Clone_Call) Run(run func(ctx context.Context, opts CloneOptions)) *MockClonableRepository_Clone_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(CloneOptions))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonableRepository_Clone_Call) Return(_a0 ClonedRepository, _a1 error) *MockClonableRepository_Clone_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonableRepository_Clone_Call) RunAndReturn(run func(context.Context, CloneOptions) (ClonedRepository, error)) *MockClonableRepository_Clone_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockClonableRepository creates a new instance of MockClonableRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockClonableRepository(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockClonableRepository {
|
||||
mock := &MockClonableRepository{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
)
|
||||
|
||||
// WrapWithCloneAndPushIfPossible clones a repository if possible, executes operations on the clone,
|
||||
// and automatically pushes changes when the function completes. For repositories that support cloning,
|
||||
// all operations are transparently executed on the clone, and the clone is automatically cleaned up
|
||||
// afterward. If cloning is not supported, the original repository instance is used directly.
|
||||
func WrapWithCloneAndPushIfPossible(
|
||||
ctx context.Context,
|
||||
repo Repository,
|
||||
cloneOptions CloneOptions,
|
||||
pushOptions PushOptions,
|
||||
fn func(repo Repository, cloned bool) error,
|
||||
) error {
|
||||
clonable, ok := repo.(ClonableRepository)
|
||||
if !ok {
|
||||
return fn(repo, false)
|
||||
}
|
||||
|
||||
clone, err := clonable.Clone(ctx, cloneOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("clone repository: %w", err)
|
||||
}
|
||||
|
||||
// We don't, we simply log it
|
||||
// FIXME: should we handle this differently?
|
||||
defer func() {
|
||||
if err := clone.Remove(ctx); err != nil {
|
||||
logging.FromContext(ctx).Error("failed to remove cloned repository after export", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := fn(clone, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return clone.Push(ctx, pushOptions)
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockCloneFn is an autogenerated mock type for the CloneFn type
|
||||
type MockCloneFn struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockCloneFn_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockCloneFn) EXPECT() *MockCloneFn_Expecter {
|
||||
return &MockCloneFn_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Execute provides a mock function with given fields: ctx, opts
|
||||
func (_m *MockCloneFn) Execute(ctx context.Context, opts CloneOptions) (ClonedRepository, error) {
|
||||
ret := _m.Called(ctx, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Execute")
|
||||
}
|
||||
|
||||
var r0 ClonedRepository
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) (ClonedRepository, error)); ok {
|
||||
return rf(ctx, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) ClonedRepository); ok {
|
||||
r0 = rf(ctx, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(ClonedRepository)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, CloneOptions) error); ok {
|
||||
r1 = rf(ctx, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockCloneFn_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute'
|
||||
type MockCloneFn_Execute_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Execute is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - opts CloneOptions
|
||||
func (_e *MockCloneFn_Expecter) Execute(ctx interface{}, opts interface{}) *MockCloneFn_Execute_Call {
|
||||
return &MockCloneFn_Execute_Call{Call: _e.mock.On("Execute", ctx, opts)}
|
||||
}
|
||||
|
||||
func (_c *MockCloneFn_Execute_Call) Run(run func(ctx context.Context, opts CloneOptions)) *MockCloneFn_Execute_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(CloneOptions))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCloneFn_Execute_Call) Return(_a0 ClonedRepository, _a1 error) *MockCloneFn_Execute_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCloneFn_Execute_Call) RunAndReturn(run func(context.Context, CloneOptions) (ClonedRepository, error)) *MockCloneFn_Execute_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockCloneFn creates a new instance of MockCloneFn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockCloneFn(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockCloneFn {
|
||||
mock := &MockCloneFn{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type mockClonableRepo struct {
|
||||
*MockClonableRepository
|
||||
*MockClonedRepository
|
||||
}
|
||||
|
||||
func Test_WrapWithCloneAndPushIfPossible_NonClonableRepository(t *testing.T) {
|
||||
nonClonable := NewMockRepository(t)
|
||||
var called bool
|
||||
fn := func(repo Repository, cloned bool) error {
|
||||
called = true
|
||||
return errors.New("operation failed")
|
||||
}
|
||||
|
||||
err := WrapWithCloneAndPushIfPossible(context.Background(), nonClonable, CloneOptions{}, PushOptions{}, fn)
|
||||
require.EqualError(t, err, "operation failed")
|
||||
require.True(t, called)
|
||||
}
|
||||
|
||||
func TestWrapWithCloneAndPushIfPossible(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMocks func(t *testing.T) *mockClonableRepo
|
||||
operation func(repo Repository, cloned bool) error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "successful clone, operation, and push",
|
||||
setupMocks: func(t *testing.T) *mockClonableRepo {
|
||||
mockRepo := NewMockClonableRepository(t)
|
||||
mockCloned := NewMockClonedRepository(t)
|
||||
|
||||
mockRepo.EXPECT().Clone(mock.Anything, CloneOptions{}).Return(mockCloned, nil)
|
||||
mockCloned.EXPECT().Push(mock.Anything, PushOptions{}).Return(nil)
|
||||
mockCloned.EXPECT().Remove(mock.Anything).Return(nil)
|
||||
return &mockClonableRepo{
|
||||
MockClonableRepository: mockRepo,
|
||||
MockClonedRepository: mockCloned,
|
||||
}
|
||||
},
|
||||
operation: func(repo Repository, cloned bool) error {
|
||||
require.True(t, cloned)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "clone failure",
|
||||
setupMocks: func(t *testing.T) *mockClonableRepo {
|
||||
mockRepo := NewMockClonableRepository(t)
|
||||
|
||||
mockRepo.EXPECT().Clone(mock.Anything, CloneOptions{}).Return(nil, errors.New("clone failed"))
|
||||
|
||||
return &mockClonableRepo{
|
||||
MockClonableRepository: mockRepo,
|
||||
}
|
||||
},
|
||||
operation: func(repo Repository, cloned bool) error {
|
||||
return nil
|
||||
},
|
||||
expectedError: "clone repository: clone failed",
|
||||
},
|
||||
{
|
||||
name: "operation failure",
|
||||
setupMocks: func(t *testing.T) *mockClonableRepo {
|
||||
mockRepo := NewMockClonableRepository(t)
|
||||
mockCloned := NewMockClonedRepository(t)
|
||||
|
||||
mockRepo.EXPECT().Clone(mock.Anything, CloneOptions{}).Return(mockCloned, nil)
|
||||
mockCloned.EXPECT().Remove(mock.Anything).Return(nil)
|
||||
|
||||
return &mockClonableRepo{
|
||||
MockClonableRepository: mockRepo,
|
||||
MockClonedRepository: mockCloned,
|
||||
}
|
||||
},
|
||||
operation: func(repo Repository, cloned bool) error {
|
||||
return errors.New("operation failed")
|
||||
},
|
||||
expectedError: "operation failed",
|
||||
},
|
||||
{
|
||||
name: "push failure",
|
||||
setupMocks: func(t *testing.T) *mockClonableRepo {
|
||||
mockRepo := NewMockClonableRepository(t)
|
||||
mockCloned := NewMockClonedRepository(t)
|
||||
|
||||
mockRepo.EXPECT().Clone(mock.Anything, CloneOptions{}).Return(mockCloned, nil)
|
||||
mockCloned.EXPECT().Push(mock.Anything, PushOptions{}).Return(errors.New("push failed"))
|
||||
mockCloned.EXPECT().Remove(mock.Anything).Return(nil)
|
||||
|
||||
return &mockClonableRepo{
|
||||
MockClonableRepository: mockRepo,
|
||||
MockClonedRepository: mockCloned,
|
||||
}
|
||||
},
|
||||
operation: func(repo Repository, cloned bool) error {
|
||||
return nil
|
||||
},
|
||||
expectedError: "push failed",
|
||||
},
|
||||
{
|
||||
name: "remove failure should only log",
|
||||
setupMocks: func(t *testing.T) *mockClonableRepo {
|
||||
mockRepo := NewMockClonableRepository(t)
|
||||
mockCloned := NewMockClonedRepository(t)
|
||||
|
||||
mockRepo.EXPECT().Clone(mock.Anything, CloneOptions{}).Return(mockCloned, nil)
|
||||
mockCloned.EXPECT().Push(mock.Anything, PushOptions{}).Return(nil)
|
||||
mockCloned.EXPECT().Remove(mock.Anything).Return(errors.New("remove failed"))
|
||||
|
||||
return &mockClonableRepo{
|
||||
MockClonableRepository: mockRepo,
|
||||
MockClonedRepository: mockCloned,
|
||||
}
|
||||
},
|
||||
operation: func(repo Repository, cloned bool) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := tt.setupMocks(t)
|
||||
err := WrapWithCloneAndPushIfPossible(context.Background(), repo, CloneOptions{}, PushOptions{}, tt.operation)
|
||||
|
||||
if tt.expectedError != "" {
|
||||
require.EqualError(t, err, tt.expectedError)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package repository
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// basicGitBranchNameRegex is a regular expression to validate a git branch name
|
||||
// it does not cover all cases as positive lookaheads are not supported in Go's regexp
|
||||
var basicGitBranchNameRegex = regexp.MustCompile(`^[a-zA-Z0-9\-\_\/\.]+$`)
|
||||
|
||||
// IsValidGitBranchName checks if a branch name is valid.
|
||||
// It uses the following regexp `^[a-zA-Z0-9\-\_\/\.]+$` to validate the branch name with some additional checks that must satisfy the following rules:
|
||||
// 1. The branch name must have at least one character and must not be empty.
|
||||
// 2. The branch name cannot start with `/` or end with `/`, `.`, or whitespace.
|
||||
// 3. The branch name cannot contain consecutive slashes (`//`).
|
||||
// 4. The branch name cannot contain consecutive dots (`..`).
|
||||
// 5. The branch name cannot contain `@{`.
|
||||
// 6. The branch name cannot include the following characters: `~`, `^`, `:`, `?`, `*`, `[`, `\`, or `]`.
|
||||
func IsValidGitBranchName(branch string) bool {
|
||||
if !basicGitBranchNameRegex.MatchString(branch) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Additional checks for invalid patterns
|
||||
if strings.HasPrefix(branch, "/") || strings.HasSuffix(branch, "/") ||
|
||||
strings.HasSuffix(branch, ".") || strings.Contains(branch, "..") ||
|
||||
strings.Contains(branch, "//") || strings.HasSuffix(branch, ".lock") {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsValidGitBranchName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
branch string
|
||||
expected bool
|
||||
}{
|
||||
{"Valid branch name", "feature/add-tests", true},
|
||||
{"Valid branch name with numbers", "feature/123-add-tests", true},
|
||||
{"Valid branch name with dots", "feature.add.tests", true},
|
||||
{"Valid branch name with hyphens", "feature-add-tests", true},
|
||||
{"Valid branch name with underscores", "feature_add_tests", true},
|
||||
{"Valid branch name with mixed characters", "feature/add_tests-123", true},
|
||||
{"Starts with /", "/feature", false},
|
||||
{"Ends with /", "feature/", false},
|
||||
{"Ends with .", "feature.", false},
|
||||
{"Ends with space", "feature ", false},
|
||||
{"Contains consecutive slashes", "feature//branch", false},
|
||||
{"Contains consecutive dots", "feature..branch", false},
|
||||
{"Contains @{", "feature@{branch", false},
|
||||
{"Contains invalid character ~", "feature~branch", false},
|
||||
{"Contains invalid character ^", "feature^branch", false},
|
||||
{"Contains invalid character :", "feature:branch", false},
|
||||
{"Contains invalid character ?", "feature?branch", false},
|
||||
{"Contains invalid character *", "feature*branch", false},
|
||||
{"Contains invalid character [", "feature[branch", false},
|
||||
{"Contains invalid character ]", "feature]branch", false},
|
||||
{"Contains invalid character \\", "feature\\branch", false},
|
||||
{"Empty branch name", "", false},
|
||||
{"Only whitespace", " ", false},
|
||||
{"Single valid character", "a", true},
|
||||
{"Ends with .lock", "feature.lock", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, IsValidGitBranchName(tt.branch))
|
||||
})
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -1,15 +1,17 @@
|
||||
package repository
|
||||
package git
|
||||
|
||||
import "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
|
||||
// GitRepository is an interface that combines all repository capabilities
|
||||
// needed for Git repositories.
|
||||
//
|
||||
//go:generate mockery --name GitRepository --structname MockGitRepository --inpackage --filename git_repository_mock.go --with-expecter
|
||||
type GitRepository interface {
|
||||
Repository
|
||||
Versioned
|
||||
Writer
|
||||
Reader
|
||||
ClonableRepository
|
||||
repository.Repository
|
||||
repository.Versioned
|
||||
repository.Writer
|
||||
repository.Reader
|
||||
repository.StageableRepository
|
||||
URL() string
|
||||
Branch() string
|
||||
}
|
||||
+84
-82
@@ -1,6 +1,6 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package repository
|
||||
package git
|
||||
|
||||
import (
|
||||
context "context"
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
@@ -69,83 +71,24 @@ func (_c *MockGitRepository_Branch_Call) RunAndReturn(run func() string) *MockGi
|
||||
return _c
|
||||
}
|
||||
|
||||
// Clone provides a mock function with given fields: ctx, opts
|
||||
func (_m *MockGitRepository) Clone(ctx context.Context, opts CloneOptions) (ClonedRepository, error) {
|
||||
ret := _m.Called(ctx, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Clone")
|
||||
}
|
||||
|
||||
var r0 ClonedRepository
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) (ClonedRepository, error)); ok {
|
||||
return rf(ctx, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) ClonedRepository); ok {
|
||||
r0 = rf(ctx, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(ClonedRepository)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, CloneOptions) error); ok {
|
||||
r1 = rf(ctx, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGitRepository_Clone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clone'
|
||||
type MockGitRepository_Clone_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Clone is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - opts CloneOptions
|
||||
func (_e *MockGitRepository_Expecter) Clone(ctx interface{}, opts interface{}) *MockGitRepository_Clone_Call {
|
||||
return &MockGitRepository_Clone_Call{Call: _e.mock.On("Clone", ctx, opts)}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Clone_Call) Run(run func(ctx context.Context, opts CloneOptions)) *MockGitRepository_Clone_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(CloneOptions))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Clone_Call) Return(_a0 ClonedRepository, _a1 error) *MockGitRepository_Clone_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Clone_Call) RunAndReturn(run func(context.Context, CloneOptions) (ClonedRepository, error)) *MockGitRepository_Clone_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CompareFiles provides a mock function with given fields: ctx, base, ref
|
||||
func (_m *MockGitRepository) CompareFiles(ctx context.Context, base string, ref string) ([]VersionedFileChange, error) {
|
||||
func (_m *MockGitRepository) CompareFiles(ctx context.Context, base string, ref string) ([]repository.VersionedFileChange, error) {
|
||||
ret := _m.Called(ctx, base, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CompareFiles")
|
||||
}
|
||||
|
||||
var r0 []VersionedFileChange
|
||||
var r0 []repository.VersionedFileChange
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]VersionedFileChange, error)); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]repository.VersionedFileChange, error)); ok {
|
||||
return rf(ctx, base, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []VersionedFileChange); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []repository.VersionedFileChange); ok {
|
||||
r0 = rf(ctx, base, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]VersionedFileChange)
|
||||
r0 = ret.Get(0).([]repository.VersionedFileChange)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,12 +121,12 @@ func (_c *MockGitRepository_CompareFiles_Call) Run(run func(ctx context.Context,
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_CompareFiles_Call) Return(_a0 []VersionedFileChange, _a1 error) *MockGitRepository_CompareFiles_Call {
|
||||
func (_c *MockGitRepository_CompareFiles_Call) Return(_a0 []repository.VersionedFileChange, _a1 error) *MockGitRepository_CompareFiles_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_CompareFiles_Call) RunAndReturn(run func(context.Context, string, string) ([]VersionedFileChange, error)) *MockGitRepository_CompareFiles_Call {
|
||||
func (_c *MockGitRepository_CompareFiles_Call) RunAndReturn(run func(context.Context, string, string) ([]repository.VersionedFileChange, error)) *MockGitRepository_CompareFiles_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -451,23 +394,23 @@ func (_c *MockGitRepository_LatestRef_Call) RunAndReturn(run func(context.Contex
|
||||
}
|
||||
|
||||
// Read provides a mock function with given fields: ctx, path, ref
|
||||
func (_m *MockGitRepository) Read(ctx context.Context, path string, ref string) (*FileInfo, error) {
|
||||
func (_m *MockGitRepository) Read(ctx context.Context, path string, ref string) (*repository.FileInfo, error) {
|
||||
ret := _m.Called(ctx, path, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Read")
|
||||
}
|
||||
|
||||
var r0 *FileInfo
|
||||
var r0 *repository.FileInfo
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*FileInfo, error)); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*repository.FileInfo, error)); ok {
|
||||
return rf(ctx, path, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) *FileInfo); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) *repository.FileInfo); ok {
|
||||
r0 = rf(ctx, path, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*FileInfo)
|
||||
r0 = ret.Get(0).(*repository.FileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,34 +443,34 @@ func (_c *MockGitRepository_Read_Call) Run(run func(ctx context.Context, path st
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Read_Call) Return(_a0 *FileInfo, _a1 error) *MockGitRepository_Read_Call {
|
||||
func (_c *MockGitRepository_Read_Call) Return(_a0 *repository.FileInfo, _a1 error) *MockGitRepository_Read_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*FileInfo, error)) *MockGitRepository_Read_Call {
|
||||
func (_c *MockGitRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*repository.FileInfo, error)) *MockGitRepository_Read_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ReadTree provides a mock function with given fields: ctx, ref
|
||||
func (_m *MockGitRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) {
|
||||
func (_m *MockGitRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
||||
ret := _m.Called(ctx, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ReadTree")
|
||||
}
|
||||
|
||||
var r0 []FileTreeEntry
|
||||
var r0 []repository.FileTreeEntry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) ([]FileTreeEntry, error)); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) ([]repository.FileTreeEntry, error)); ok {
|
||||
return rf(ctx, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) []FileTreeEntry); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) []repository.FileTreeEntry); ok {
|
||||
r0 = rf(ctx, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]FileTreeEntry)
|
||||
r0 = ret.Get(0).([]repository.FileTreeEntry)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,12 +502,71 @@ func (_c *MockGitRepository_ReadTree_Call) Run(run func(ctx context.Context, ref
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_ReadTree_Call) Return(_a0 []FileTreeEntry, _a1 error) *MockGitRepository_ReadTree_Call {
|
||||
func (_c *MockGitRepository_ReadTree_Call) Return(_a0 []repository.FileTreeEntry, _a1 error) *MockGitRepository_ReadTree_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]FileTreeEntry, error)) *MockGitRepository_ReadTree_Call {
|
||||
func (_c *MockGitRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]repository.FileTreeEntry, error)) *MockGitRepository_ReadTree_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Stage provides a mock function with given fields: ctx, opts
|
||||
func (_m *MockGitRepository) Stage(ctx context.Context, opts repository.StageOptions) (repository.StagedRepository, error) {
|
||||
ret := _m.Called(ctx, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Stage")
|
||||
}
|
||||
|
||||
var r0 repository.StagedRepository
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, repository.StageOptions) (repository.StagedRepository, error)); ok {
|
||||
return rf(ctx, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, repository.StageOptions) repository.StagedRepository); ok {
|
||||
r0 = rf(ctx, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(repository.StagedRepository)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, repository.StageOptions) error); ok {
|
||||
r1 = rf(ctx, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGitRepository_Stage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stage'
|
||||
type MockGitRepository_Stage_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Stage is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - opts repository.StageOptions
|
||||
func (_e *MockGitRepository_Expecter) Stage(ctx interface{}, opts interface{}) *MockGitRepository_Stage_Call {
|
||||
return &MockGitRepository_Stage_Call{Call: _e.mock.On("Stage", ctx, opts)}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Stage_Call) Run(run func(ctx context.Context, opts repository.StageOptions)) *MockGitRepository_Stage_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(repository.StageOptions))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Stage_Call) Return(_a0 repository.StagedRepository, _a1 error) *MockGitRepository_Stage_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Stage_Call) RunAndReturn(run func(context.Context, repository.StageOptions) (repository.StagedRepository, error)) *MockGitRepository_Stage_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
+24
-24
@@ -1,6 +1,7 @@
|
||||
package nanogit
|
||||
package git
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -18,7 +19,6 @@ import (
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
||||
"github.com/grafana/nanogit"
|
||||
"github.com/grafana/nanogit/log"
|
||||
"github.com/grafana/nanogit/options"
|
||||
@@ -39,28 +39,19 @@ type gitRepository struct {
|
||||
config *provisioning.Repository
|
||||
gitConfig RepositoryConfig
|
||||
client nanogit.Client
|
||||
secrets secrets.Service
|
||||
}
|
||||
|
||||
func NewGitRepository(
|
||||
ctx context.Context,
|
||||
secrets secrets.Service,
|
||||
config *provisioning.Repository,
|
||||
gitConfig RepositoryConfig,
|
||||
) (repository.GitRepository, error) {
|
||||
if gitConfig.Token == "" {
|
||||
decrypted, err := secrets.Decrypt(ctx, gitConfig.EncryptedToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt token: %w", err)
|
||||
}
|
||||
gitConfig.Token = string(decrypted)
|
||||
) (GitRepository, error) {
|
||||
var opts []options.Option
|
||||
if len(gitConfig.Token) > 0 {
|
||||
opts = append(opts, options.WithBasicAuth("git", gitConfig.Token))
|
||||
}
|
||||
|
||||
// Create nanogit client with authentication
|
||||
client, err := nanogit.NewHTTPClient(
|
||||
gitConfig.URL,
|
||||
options.WithBasicAuth("git", gitConfig.Token),
|
||||
)
|
||||
client, err := nanogit.NewHTTPClient(gitConfig.URL, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create nanogit client: %w", err)
|
||||
}
|
||||
@@ -69,7 +60,6 @@ func NewGitRepository(
|
||||
config: config,
|
||||
gitConfig: gitConfig,
|
||||
client: client,
|
||||
secrets: secrets,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -99,12 +89,15 @@ func (r *gitRepository) Validate() (list field.ErrorList) {
|
||||
}
|
||||
if cfg.Branch == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", t, "branch"), "a git branch is required"))
|
||||
} else if !repository.IsValidGitBranchName(cfg.Branch) {
|
||||
} else if !IsValidGitBranchName(cfg.Branch) {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", t, "branch"), cfg.Branch, "invalid branch name"))
|
||||
}
|
||||
|
||||
if cfg.Token == "" && len(cfg.EncryptedToken) == 0 {
|
||||
list = append(list, field.Required(field.NewPath("spec", t, "token"), "a git access token is required"))
|
||||
// If the repository has workflows, we require a token or encrypted token
|
||||
if len(r.config.Spec.Workflows) > 0 {
|
||||
if cfg.Token == "" && len(cfg.EncryptedToken) == 0 {
|
||||
list = append(list, field.Required(field.NewPath("spec", t, "token"), "a git access token is required"))
|
||||
}
|
||||
}
|
||||
|
||||
if err := safepath.IsSafe(cfg.Path); err != nil {
|
||||
@@ -413,11 +406,15 @@ func (r *gitRepository) Write(ctx context.Context, path string, ref string, data
|
||||
}
|
||||
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
_, err := r.Read(ctx, path, ref)
|
||||
info, err := r.Read(ctx, path, ref)
|
||||
if err != nil && !(errors.Is(err, repository.ErrFileNotFound)) {
|
||||
return fmt.Errorf("check if file exists before writing: %w", err)
|
||||
}
|
||||
if err == nil {
|
||||
// If the value already exists and is the same, we don't need to do anything
|
||||
if bytes.Equal(info.Data, data) {
|
||||
return nil
|
||||
}
|
||||
return r.Update(ctx, path, ref, data, message)
|
||||
}
|
||||
|
||||
@@ -584,7 +581,7 @@ func (r *gitRepository) CompareFiles(ctx context.Context, base, ref string) ([]r
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func (r *gitRepository) Clone(ctx context.Context, opts repository.CloneOptions) (repository.ClonedRepository, error) {
|
||||
func (r *gitRepository) Stage(ctx context.Context, opts repository.StageOptions) (repository.StagedRepository, error) {
|
||||
return NewStagedGitRepository(ctx, r, opts)
|
||||
}
|
||||
|
||||
@@ -592,7 +589,7 @@ func (r *gitRepository) Clone(ctx context.Context, opts repository.CloneOptions)
|
||||
func (r *gitRepository) resolveRefToHash(ctx context.Context, ref string) (hash.Hash, error) {
|
||||
// Use default branch if ref is empty
|
||||
if ref == "" {
|
||||
ref = fmt.Sprintf("refs/heads/%s", r.gitConfig.Branch)
|
||||
ref = r.gitConfig.Branch
|
||||
}
|
||||
|
||||
// Try to parse ref as a hash first
|
||||
@@ -602,6 +599,9 @@ func (r *gitRepository) resolveRefToHash(ctx context.Context, ref string) (hash.
|
||||
return refHash, nil
|
||||
}
|
||||
|
||||
// Prefix ref with refs/heads/
|
||||
ref = fmt.Sprintf("refs/heads/%s", ref)
|
||||
|
||||
// Not a valid hash, try to resolve as a branch reference
|
||||
branchRef, err := r.client.GetRef(ctx, ref)
|
||||
if err != nil {
|
||||
@@ -617,7 +617,7 @@ func (r *gitRepository) resolveRefToHash(ctx context.Context, ref string) (hash.
|
||||
// ensureBranchExists checks if a branch exists and creates it if it doesn't,
|
||||
// returning the branch reference to avoid duplicate GetRef calls
|
||||
func (r *gitRepository) ensureBranchExists(ctx context.Context, branchName string) (nanogit.Ref, error) {
|
||||
if !repository.IsValidGitBranchName(branchName) {
|
||||
if !IsValidGitBranchName(branchName) {
|
||||
return nanogit.Ref{}, &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Code: http.StatusBadRequest,
|
||||
+40
-125
@@ -1,4 +1,4 @@
|
||||
package nanogit
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -7,17 +7,17 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
||||
"github.com/grafana/nanogit"
|
||||
"github.com/grafana/nanogit/mocks"
|
||||
"github.com/grafana/nanogit/protocol"
|
||||
"github.com/grafana/nanogit/protocol/hash"
|
||||
"github.com/stretchr/testify/require"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
"github.com/grafana/nanogit"
|
||||
"github.com/grafana/nanogit/mocks"
|
||||
"github.com/grafana/nanogit/protocol"
|
||||
"github.com/grafana/nanogit/protocol/hash"
|
||||
)
|
||||
|
||||
func TestGitRepository_Validate(t *testing.T) {
|
||||
@@ -138,10 +138,11 @@ func TestGitRepository_Validate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing token",
|
||||
name: "missing token for R/W repository",
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Type: "test_type",
|
||||
Type: "test_type",
|
||||
Workflows: []provisioning.Workflow{provisioning.WriteWorkflow},
|
||||
},
|
||||
},
|
||||
gitConfig: RepositoryConfig{
|
||||
@@ -153,6 +154,21 @@ func TestGitRepository_Validate(t *testing.T) {
|
||||
field.Required(field.NewPath("spec", "test_type", "token"), "a git access token is required"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing token for read-only repository",
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Type: "test_type",
|
||||
Workflows: nil, // read-only
|
||||
},
|
||||
},
|
||||
gitConfig: RepositoryConfig{
|
||||
URL: "https://git.example.com/repo.git",
|
||||
Branch: "main",
|
||||
Token: "", // Empty token
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "unsafe path",
|
||||
config: &provisioning.Repository{
|
||||
@@ -258,20 +274,8 @@ func TestIsValidGitURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mock secrets service for testing
|
||||
type mockSecretsService struct{}
|
||||
|
||||
func (m *mockSecretsService) Decrypt(ctx context.Context, data []byte) ([]byte, error) {
|
||||
return []byte("decrypted-token"), nil
|
||||
}
|
||||
|
||||
func (m *mockSecretsService) Encrypt(ctx context.Context, data []byte) ([]byte, error) {
|
||||
return []byte("encrypted-token"), nil
|
||||
}
|
||||
|
||||
func TestNewGit(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockSecrets := &mockSecretsService{}
|
||||
|
||||
config := &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
@@ -288,7 +292,7 @@ func TestNewGit(t *testing.T) {
|
||||
|
||||
// This should succeed in creating the client but won't be able to connect
|
||||
// We just test that the basic structure is created correctly
|
||||
gitRepo, err := NewGitRepository(ctx, mockSecrets, config, gitConfig)
|
||||
gitRepo, err := NewGitRepository(ctx, config, gitConfig)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, gitRepo)
|
||||
require.Equal(t, "https://git.example.com/owner/repo.git", gitRepo.URL())
|
||||
@@ -1737,57 +1741,21 @@ func TestGitRepository_createSignature(t *testing.T) {
|
||||
|
||||
func TestNewGitRepository(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mockSecretsService)
|
||||
gitConfig RepositoryConfig
|
||||
wantError bool
|
||||
expectURL string
|
||||
expectToken string
|
||||
name string
|
||||
gitConfig RepositoryConfig
|
||||
wantError bool
|
||||
expectURL string
|
||||
}{
|
||||
{
|
||||
name: "success - with token",
|
||||
setupMock: func(mockSecrets *mockSecretsService) {
|
||||
// No setup needed for token case
|
||||
},
|
||||
gitConfig: RepositoryConfig{
|
||||
URL: "https://git.example.com/owner/repo.git",
|
||||
Branch: "main",
|
||||
Token: "plain-token",
|
||||
Path: "configs",
|
||||
},
|
||||
wantError: false,
|
||||
expectURL: "https://git.example.com/owner/repo.git",
|
||||
expectToken: "plain-token",
|
||||
},
|
||||
{
|
||||
name: "success - with encrypted token",
|
||||
setupMock: func(mockSecrets *mockSecretsService) {
|
||||
// Mock will return decrypted token
|
||||
},
|
||||
gitConfig: RepositoryConfig{
|
||||
URL: "https://git.example.com/owner/repo.git",
|
||||
Branch: "main",
|
||||
Token: "", // Empty token, will use encrypted
|
||||
EncryptedToken: []byte("encrypted-token"),
|
||||
Path: "configs",
|
||||
},
|
||||
wantError: false,
|
||||
expectURL: "https://git.example.com/owner/repo.git",
|
||||
expectToken: "decrypted-token", // From mock
|
||||
},
|
||||
{
|
||||
name: "failure - decryption error",
|
||||
setupMock: func(mockSecrets *mockSecretsService) {
|
||||
// This test will use the separate mockSecretsServiceWithError
|
||||
},
|
||||
gitConfig: RepositoryConfig{
|
||||
URL: "https://git.example.com/owner/repo.git",
|
||||
Branch: "main",
|
||||
Token: "",
|
||||
EncryptedToken: []byte("bad-encrypted-token"),
|
||||
Path: "configs",
|
||||
},
|
||||
wantError: true,
|
||||
wantError: false,
|
||||
expectURL: "https://git.example.com/owner/repo.git",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1795,20 +1763,13 @@ func TestNewGitRepository(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
var mockSecrets secrets.Service
|
||||
if tt.name == "failure - decryption error" {
|
||||
mockSecrets = &mockSecretsServiceWithError{shouldError: true}
|
||||
} else {
|
||||
mockSecrets = &mockSecretsService{}
|
||||
}
|
||||
|
||||
config := &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Type: provisioning.GitHubRepositoryType,
|
||||
},
|
||||
}
|
||||
|
||||
gitRepo, err := NewGitRepository(ctx, mockSecrets, config, tt.gitConfig)
|
||||
gitRepo, err := NewGitRepository(ctx, config, tt.gitConfig)
|
||||
|
||||
if tt.wantError {
|
||||
require.Error(t, err)
|
||||
@@ -2176,7 +2137,7 @@ func TestGitRepository_logger(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGitRepository_Clone(t *testing.T) {
|
||||
func TestGitRepository_Stage(t *testing.T) {
|
||||
gitRepo := &gitRepository{
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
@@ -2192,9 +2153,8 @@ func TestGitRepository_Clone(t *testing.T) {
|
||||
|
||||
t.Run("calls NewStagedGitRepository", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
opts := repository.CloneOptions{
|
||||
CreateIfNotExists: true,
|
||||
PushOnWrites: true,
|
||||
opts := repository.StageOptions{
|
||||
PushOnWrites: true,
|
||||
}
|
||||
|
||||
// Since NewStagedGitRepository is not mocked and may panic, we expect this to fail
|
||||
@@ -2206,11 +2166,11 @@ func TestGitRepository_Clone(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
cloned, err := gitRepo.Clone(ctx, opts)
|
||||
staged, err := gitRepo.Stage(ctx, opts)
|
||||
|
||||
// This will likely error/panic since we don't have a real implementation
|
||||
// but we're testing that the method exists and forwards to NewStagedGitRepository
|
||||
_ = cloned
|
||||
_ = staged
|
||||
_ = err
|
||||
})
|
||||
}
|
||||
@@ -2279,50 +2239,6 @@ func TestGitRepository_EdgeCases(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Enhanced secrets service mock with error handling
|
||||
type mockSecretsServiceWithError struct {
|
||||
shouldError bool
|
||||
}
|
||||
|
||||
func (m *mockSecretsServiceWithError) Decrypt(ctx context.Context, data []byte) ([]byte, error) {
|
||||
if m.shouldError {
|
||||
return nil, errors.New("decryption failed")
|
||||
}
|
||||
return []byte("decrypted-token"), nil
|
||||
}
|
||||
|
||||
func (m *mockSecretsServiceWithError) Encrypt(ctx context.Context, data []byte) ([]byte, error) {
|
||||
if m.shouldError {
|
||||
return nil, errors.New("encryption failed")
|
||||
}
|
||||
return []byte("encrypted-token"), nil
|
||||
}
|
||||
|
||||
func TestNewGitRepository_DecryptionError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockSecrets := &mockSecretsServiceWithError{shouldError: true}
|
||||
|
||||
config := &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Type: provisioning.GitHubRepositoryType,
|
||||
},
|
||||
}
|
||||
|
||||
gitConfig := RepositoryConfig{
|
||||
URL: "https://git.example.com/owner/repo.git",
|
||||
Branch: "main",
|
||||
Token: "",
|
||||
EncryptedToken: []byte("encrypted-token"),
|
||||
Path: "configs",
|
||||
}
|
||||
|
||||
gitRepo, err := NewGitRepository(ctx, mockSecrets, config, gitConfig)
|
||||
|
||||
require.Error(t, err)
|
||||
require.Nil(t, gitRepo)
|
||||
require.Contains(t, err.Error(), "decrypt token")
|
||||
}
|
||||
|
||||
func TestGitRepository_ValidateBranchNames(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -2790,7 +2706,6 @@ func TestGitRepository_NewGitRepository_ClientError(t *testing.T) {
|
||||
// This test would require mocking nanogit.NewHTTPClient which is difficult
|
||||
// We test the path where the client creation would fail by using invalid URL
|
||||
ctx := context.Background()
|
||||
mockSecrets := &mockSecretsService{}
|
||||
|
||||
config := &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
@@ -2805,7 +2720,7 @@ func TestGitRepository_NewGitRepository_ClientError(t *testing.T) {
|
||||
Path: "configs",
|
||||
}
|
||||
|
||||
gitRepo, err := NewGitRepository(ctx, mockSecrets, config, gitConfig)
|
||||
gitRepo, err := NewGitRepository(ctx, config, gitConfig)
|
||||
|
||||
// We expect this to fail during client creation
|
||||
require.Error(t, err)
|
||||
+21
-26
@@ -1,4 +1,4 @@
|
||||
package nanogit
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -15,17 +15,11 @@ import (
|
||||
// once that happens we could do more magic here.
|
||||
type stagedGitRepository struct {
|
||||
*gitRepository
|
||||
opts repository.CloneOptions
|
||||
opts repository.StageOptions
|
||||
writer nanogit.StagedWriter
|
||||
}
|
||||
|
||||
func NewStagedGitRepository(ctx context.Context, repo *gitRepository, opts repository.CloneOptions) (repository.ClonedRepository, error) {
|
||||
if opts.BeforeFn != nil {
|
||||
if err := opts.BeforeFn(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func NewStagedGitRepository(ctx context.Context, repo *gitRepository, opts repository.StageOptions) (repository.StagedRepository, error) {
|
||||
if opts.Timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
|
||||
@@ -89,28 +83,35 @@ func (r *stagedGitRepository) Create(ctx context.Context, path, ref string, data
|
||||
}
|
||||
|
||||
if r.opts.PushOnWrites {
|
||||
return r.Push(ctx, repository.PushOptions{})
|
||||
return r.Push(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *stagedGitRepository) blobExists(ctx context.Context, path string) (bool, error) {
|
||||
if r.gitConfig.Path != "" {
|
||||
path = safepath.Join(r.gitConfig.Path, path)
|
||||
}
|
||||
return r.writer.BlobExists(ctx, path)
|
||||
}
|
||||
|
||||
func (r *stagedGitRepository) Write(ctx context.Context, path, ref string, data []byte, message string) error {
|
||||
if ref != "" && ref != r.gitConfig.Branch {
|
||||
return errors.New("ref is not supported for staged repository")
|
||||
}
|
||||
|
||||
ok, err := r.writer.BlobExists(ctx, path)
|
||||
exists, err := r.blobExists(ctx, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check if file exists: %w", err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
if err := r.create(ctx, path, data, r.writer); err != nil {
|
||||
if exists {
|
||||
if err := r.update(ctx, path, data, r.writer); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := r.update(ctx, path, data, r.writer); err != nil {
|
||||
if err := r.create(ctx, path, data, r.writer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -120,7 +121,7 @@ func (r *stagedGitRepository) Write(ctx context.Context, path, ref string, data
|
||||
}
|
||||
|
||||
if r.opts.PushOnWrites {
|
||||
return r.Push(ctx, repository.PushOptions{})
|
||||
return r.Push(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -144,7 +145,7 @@ func (r *stagedGitRepository) Update(ctx context.Context, path, ref string, data
|
||||
}
|
||||
|
||||
if r.opts.PushOnWrites {
|
||||
return r.Push(ctx, repository.PushOptions{})
|
||||
return r.Push(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -164,22 +165,16 @@ func (r *stagedGitRepository) Delete(ctx context.Context, path, ref, message str
|
||||
}
|
||||
|
||||
if r.opts.PushOnWrites {
|
||||
return r.Push(ctx, repository.PushOptions{})
|
||||
return r.Push(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *stagedGitRepository) Push(ctx context.Context, opts repository.PushOptions) error {
|
||||
if opts.BeforeFn != nil {
|
||||
if err := opts.BeforeFn(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Timeout > 0 {
|
||||
func (r *stagedGitRepository) Push(ctx context.Context) error {
|
||||
if r.opts.Timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
|
||||
ctx, cancel = context.WithTimeout(ctx, r.opts.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
+46
-95
@@ -1,4 +1,4 @@
|
||||
package nanogit
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -18,7 +18,7 @@ func TestNewStagedGitRepository(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeClient)
|
||||
opts repository.CloneOptions
|
||||
opts repository.StageOptions
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
@@ -31,9 +31,8 @@ func TestNewStagedGitRepository(t *testing.T) {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
CreateIfNotExists: false,
|
||||
PushOnWrites: false,
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
wantError: nil,
|
||||
},
|
||||
@@ -47,12 +46,8 @@ func TestNewStagedGitRepository(t *testing.T) {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
CreateIfNotExists: false,
|
||||
PushOnWrites: false,
|
||||
BeforeFn: func() error {
|
||||
return nil
|
||||
},
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
wantError: nil,
|
||||
},
|
||||
@@ -66,33 +61,19 @@ func TestNewStagedGitRepository(t *testing.T) {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
CreateIfNotExists: false,
|
||||
PushOnWrites: false,
|
||||
Timeout: time.Second * 5,
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
Timeout: time.Second * 5,
|
||||
},
|
||||
wantError: nil,
|
||||
},
|
||||
{
|
||||
name: "fails with BeforeFn error",
|
||||
setupMock: func(mockClient *mocks.FakeClient) {
|
||||
// No setup needed as BeforeFn fails first
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
BeforeFn: func() error {
|
||||
return errors.New("before function failed")
|
||||
},
|
||||
},
|
||||
wantError: errors.New("before function failed"),
|
||||
},
|
||||
{
|
||||
name: "fails with GetRef error",
|
||||
setupMock: func(mockClient *mocks.FakeClient) {
|
||||
mockClient.GetRefReturns(nanogit.Ref{}, errors.New("ref not found"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
CreateIfNotExists: false,
|
||||
PushOnWrites: false,
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
wantError: errors.New("ref not found"),
|
||||
},
|
||||
@@ -105,9 +86,8 @@ func TestNewStagedGitRepository(t *testing.T) {
|
||||
}, nil)
|
||||
mockClient.NewStagedWriterReturns(nil, errors.New("failed to create writer"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
CreateIfNotExists: false,
|
||||
PushOnWrites: false,
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
wantError: errors.New("build staged writer: failed to create writer"),
|
||||
},
|
||||
@@ -141,13 +121,8 @@ func TestNewStagedGitRepository(t *testing.T) {
|
||||
|
||||
// Compare opts fields individually since function pointers can't be compared directly
|
||||
actualOpts := stagedRepo.(*stagedGitRepository).opts
|
||||
require.Equal(t, tt.opts.CreateIfNotExists, actualOpts.CreateIfNotExists)
|
||||
require.Equal(t, tt.opts.PushOnWrites, actualOpts.PushOnWrites)
|
||||
require.Equal(t, tt.opts.MaxSize, actualOpts.MaxSize)
|
||||
require.Equal(t, tt.opts.Timeout, actualOpts.Timeout)
|
||||
require.Equal(t, tt.opts.Progress, actualOpts.Progress)
|
||||
// BeforeFn is a function pointer, so we just check if both are nil or both are not nil
|
||||
require.Equal(t, tt.opts.BeforeFn == nil, actualOpts.BeforeFn == nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -287,7 +262,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeStagedWriter)
|
||||
opts repository.CloneOptions
|
||||
opts repository.StageOptions
|
||||
path string
|
||||
ref string
|
||||
data []byte
|
||||
@@ -301,7 +276,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -318,7 +293,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: true,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -333,7 +308,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
// No setup needed as error occurs before writer calls
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -347,7 +322,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.CreateBlobReturns(hash.Hash{}, errors.New("create blob failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -362,7 +337,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -378,7 +353,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
mockWriter.PushReturns(errors.New("push failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: true,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -419,7 +394,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeStagedWriter)
|
||||
opts repository.CloneOptions
|
||||
opts repository.StageOptions
|
||||
path string
|
||||
ref string
|
||||
data []byte
|
||||
@@ -435,7 +410,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -454,7 +429,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: true,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -470,7 +445,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
// No setup needed as error occurs before writer calls
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -484,7 +459,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.BlobExistsReturns(false, errors.New("blob exists check failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -499,7 +474,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
||||
mockWriter.BlobExistsReturns(false, nil)
|
||||
mockWriter.CreateBlobReturns(hash.Hash{}, errors.New("create failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -514,7 +489,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
||||
mockWriter.BlobExistsReturns(true, nil)
|
||||
mockWriter.UpdateBlobReturns(hash.Hash{}, errors.New("update failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -530,7 +505,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -570,7 +545,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeStagedWriter)
|
||||
opts repository.CloneOptions
|
||||
opts repository.StageOptions
|
||||
path string
|
||||
ref string
|
||||
data []byte
|
||||
@@ -584,7 +559,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
||||
mockWriter.UpdateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -601,7 +576,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: true,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -616,7 +591,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
// No setup needed as error occurs before writer calls
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -630,7 +605,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
// No setup needed as error occurs before writer calls
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "directory/",
|
||||
@@ -644,7 +619,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.UpdateBlobReturns(hash.Hash{}, errors.New("update blob failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -659,7 +634,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
||||
mockWriter.UpdateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -699,7 +674,7 @@ func TestStagedGitRepository_Delete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeStagedWriter)
|
||||
opts repository.CloneOptions
|
||||
opts repository.StageOptions
|
||||
path string
|
||||
ref string
|
||||
message string
|
||||
@@ -712,7 +687,7 @@ func TestStagedGitRepository_Delete(t *testing.T) {
|
||||
mockWriter.DeleteBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -728,7 +703,7 @@ func TestStagedGitRepository_Delete(t *testing.T) {
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: true,
|
||||
},
|
||||
path: "testdir/",
|
||||
@@ -742,7 +717,7 @@ func TestStagedGitRepository_Delete(t *testing.T) {
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
// No setup needed as error occurs before writer calls
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -755,7 +730,7 @@ func TestStagedGitRepository_Delete(t *testing.T) {
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.DeleteBlobReturns(hash.Hash{}, errors.New("delete blob failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -769,7 +744,7 @@ func TestStagedGitRepository_Delete(t *testing.T) {
|
||||
mockWriter.DeleteBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
opts: repository.StageOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
@@ -808,7 +783,6 @@ func TestStagedGitRepository_Push(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeStagedWriter)
|
||||
opts repository.PushOptions
|
||||
wantError error
|
||||
expectCalls int
|
||||
}{
|
||||
@@ -817,7 +791,6 @@ func TestStagedGitRepository_Push(t *testing.T) {
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.PushOptions{},
|
||||
wantError: nil,
|
||||
expectCalls: 1,
|
||||
},
|
||||
@@ -826,11 +799,6 @@ func TestStagedGitRepository_Push(t *testing.T) {
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.PushOptions{
|
||||
BeforeFn: func() error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantError: nil,
|
||||
expectCalls: 1,
|
||||
},
|
||||
@@ -839,31 +807,14 @@ func TestStagedGitRepository_Push(t *testing.T) {
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.PushOptions{
|
||||
Timeout: time.Second * 5,
|
||||
},
|
||||
wantError: nil,
|
||||
expectCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "fails with before fn error",
|
||||
setupMock: func(_ *mocks.FakeStagedWriter) {
|
||||
// No setup needed as BeforeFn fails first
|
||||
},
|
||||
opts: repository.PushOptions{
|
||||
BeforeFn: func() error {
|
||||
return errors.New("before function failed")
|
||||
},
|
||||
},
|
||||
wantError: errors.New("before function failed"),
|
||||
expectCalls: 0,
|
||||
},
|
||||
{
|
||||
name: "fails with push error",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.PushReturns(errors.New("push failed"))
|
||||
},
|
||||
opts: repository.PushOptions{},
|
||||
wantError: errors.New("push failed"),
|
||||
expectCalls: 1,
|
||||
},
|
||||
@@ -874,9 +825,9 @@ func TestStagedGitRepository_Push(t *testing.T) {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
tt.setupMock(mockWriter)
|
||||
|
||||
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, repository.CloneOptions{})
|
||||
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, repository.StageOptions{})
|
||||
|
||||
err := stagedRepo.Push(context.Background(), tt.opts)
|
||||
err := stagedRepo.Push(context.Background())
|
||||
|
||||
if tt.wantError != nil {
|
||||
require.EqualError(t, err, tt.wantError.Error())
|
||||
@@ -892,7 +843,7 @@ func TestStagedGitRepository_Push(t *testing.T) {
|
||||
func TestStagedGitRepository_Remove(t *testing.T) {
|
||||
t.Run("succeeds with remove", func(t *testing.T) {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, repository.CloneOptions{})
|
||||
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, repository.StageOptions{})
|
||||
|
||||
err := stagedRepo.Remove(context.Background())
|
||||
require.NoError(t, err)
|
||||
@@ -904,10 +855,10 @@ func TestStagedGitRepository_Remove(t *testing.T) {
|
||||
|
||||
func createTestStagedRepository(mockClient *mocks.FakeClient) *stagedGitRepository {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
return createTestStagedRepositoryWithWriter(mockWriter, repository.CloneOptions{}, mockClient)
|
||||
return createTestStagedRepositoryWithWriter(mockWriter, repository.StageOptions{}, mockClient)
|
||||
}
|
||||
|
||||
func createTestStagedRepositoryWithWriter(mockWriter *mocks.FakeStagedWriter, opts repository.CloneOptions, mockClient ...*mocks.FakeClient) *stagedGitRepository {
|
||||
func createTestStagedRepositoryWithWriter(mockWriter *mocks.FakeStagedWriter, opts repository.StageOptions, mockClient ...*mocks.FakeClient) *stagedGitRepository {
|
||||
var client nanogit.Client
|
||||
if len(mockClient) > 0 {
|
||||
client = mockClient[0]
|
||||
@@ -1,683 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
pgh "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
||||
)
|
||||
|
||||
// Make sure all public functions of this struct call the (*githubRepository).logger function, to ensure the GH repo details are included.
|
||||
type githubRepository struct {
|
||||
config *provisioning.Repository
|
||||
gh pgh.Client // assumes github.com base URL
|
||||
secrets secrets.Service
|
||||
|
||||
owner string
|
||||
repo string
|
||||
|
||||
cloneFn CloneFn
|
||||
}
|
||||
|
||||
// GithubRepository is an interface that combines all repository capabilities
|
||||
// needed for GitHub repositories.
|
||||
|
||||
//go:generate mockery --name GithubRepository --structname MockGithubRepository --inpackage --filename github_repository_mock.go --with-expecter
|
||||
type GithubRepository interface {
|
||||
Repository
|
||||
Versioned
|
||||
Writer
|
||||
Reader
|
||||
RepositoryWithURLs
|
||||
ClonableRepository
|
||||
Owner() string
|
||||
Repo() string
|
||||
Client() pgh.Client
|
||||
}
|
||||
|
||||
func NewGitHub(
|
||||
ctx context.Context,
|
||||
config *provisioning.Repository,
|
||||
factory *pgh.Factory,
|
||||
secrets secrets.Service,
|
||||
cloneFn CloneFn,
|
||||
) (GithubRepository, error) {
|
||||
owner, repo, err := ParseOwnerRepoGithub(config.Spec.GitHub.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse owner and repo: %w", err)
|
||||
}
|
||||
|
||||
token := config.Spec.GitHub.Token
|
||||
if token == "" {
|
||||
decrypted, err := secrets.Decrypt(ctx, config.Spec.GitHub.EncryptedToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt token: %w", err)
|
||||
}
|
||||
token = string(decrypted)
|
||||
}
|
||||
|
||||
return &githubRepository{
|
||||
config: config,
|
||||
gh: factory.New(ctx, token), // TODO, baseURL from config
|
||||
secrets: secrets,
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
cloneFn: cloneFn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *githubRepository) Config() *provisioning.Repository {
|
||||
return r.config
|
||||
}
|
||||
|
||||
func (r *githubRepository) Owner() string {
|
||||
return r.owner
|
||||
}
|
||||
|
||||
func (r *githubRepository) Repo() string {
|
||||
return r.repo
|
||||
}
|
||||
|
||||
func (r *githubRepository) Client() pgh.Client {
|
||||
return r.gh
|
||||
}
|
||||
|
||||
// Validate implements provisioning.Repository.
|
||||
func (r *githubRepository) Validate() (list field.ErrorList) {
|
||||
gh := r.config.Spec.GitHub
|
||||
if gh == nil {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github"), "a github config is required"))
|
||||
return list
|
||||
}
|
||||
if gh.URL == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github", "url"), "a github url is required"))
|
||||
} else {
|
||||
_, _, err := ParseOwnerRepoGithub(gh.URL)
|
||||
if err != nil {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, err.Error()))
|
||||
} else if !strings.HasPrefix(gh.URL, "https://github.com/") {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, "URL must start with https://github.com/"))
|
||||
}
|
||||
}
|
||||
if gh.Branch == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github", "branch"), "a github branch is required"))
|
||||
} else if !IsValidGitBranchName(gh.Branch) {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "branch"), gh.Branch, "invalid branch name"))
|
||||
}
|
||||
// TODO: Use two fields for token
|
||||
if gh.Token == "" && len(gh.EncryptedToken) == 0 {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github", "token"), "a github access token is required"))
|
||||
}
|
||||
|
||||
if err := safepath.IsSafe(gh.Path); err != nil {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "prefix"), gh.Path, err.Error()))
|
||||
}
|
||||
|
||||
if safepath.IsAbs(gh.Path) {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "prefix"), gh.Path, "path must be relative"))
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func ParseOwnerRepoGithub(giturl string) (owner string, repo string, err error) {
|
||||
parsed, e := url.Parse(strings.TrimSuffix(giturl, ".git"))
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
parts := strings.Split(parsed.Path, "/")
|
||||
if len(parts) < 3 {
|
||||
err = fmt.Errorf("unable to parse repo+owner from url")
|
||||
return
|
||||
}
|
||||
return parts[1], parts[2], nil
|
||||
}
|
||||
|
||||
// Test implements provisioning.Repository.
|
||||
func (r *githubRepository) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
||||
if err := r.gh.IsAuthenticated(ctx); err != nil {
|
||||
return &provisioning.TestResults{
|
||||
Code: http.StatusBadRequest,
|
||||
Success: false,
|
||||
Errors: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseTypeFieldValueInvalid,
|
||||
Field: field.NewPath("spec", "github", "token").String(),
|
||||
Detail: err.Error(),
|
||||
}}}, nil
|
||||
}
|
||||
|
||||
url := r.config.Spec.GitHub.URL
|
||||
owner, repo, err := ParseOwnerRepoGithub(url)
|
||||
if err != nil {
|
||||
return fromFieldError(field.Invalid(
|
||||
field.NewPath("spec", "github", "url"), url, err.Error())), nil
|
||||
}
|
||||
|
||||
// FIXME: check token permissions
|
||||
ok, err := r.gh.RepoExists(ctx, owner, repo)
|
||||
if err != nil {
|
||||
return fromFieldError(field.Invalid(
|
||||
field.NewPath("spec", "github", "url"), url, err.Error())), nil
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return fromFieldError(field.NotFound(
|
||||
field.NewPath("spec", "github", "url"), url)), nil
|
||||
}
|
||||
|
||||
branch := r.config.Spec.GitHub.Branch
|
||||
ok, err = r.gh.BranchExists(ctx, r.owner, r.repo, branch)
|
||||
if err != nil {
|
||||
return fromFieldError(field.Invalid(
|
||||
field.NewPath("spec", "github", "branch"), branch, err.Error())), nil
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return fromFieldError(field.NotFound(
|
||||
field.NewPath("spec", "github", "branch"), branch)), nil
|
||||
}
|
||||
|
||||
return &provisioning.TestResults{
|
||||
Code: http.StatusOK,
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadResource implements provisioning.Repository.
|
||||
func (r *githubRepository) Read(ctx context.Context, filePath, ref string) (*FileInfo, error) {
|
||||
if ref == "" {
|
||||
ref = r.config.Spec.GitHub.Branch
|
||||
}
|
||||
|
||||
finalPath := safepath.Join(r.config.Spec.GitHub.Path, filePath)
|
||||
content, dirContent, err := r.gh.GetContents(ctx, r.owner, r.repo, finalPath, ref)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgh.ErrResourceNotFound) {
|
||||
return nil, ErrFileNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("get contents: %w", err)
|
||||
}
|
||||
if dirContent != nil {
|
||||
return &FileInfo{
|
||||
Path: filePath,
|
||||
Ref: ref,
|
||||
}, nil
|
||||
}
|
||||
|
||||
data, err := content.GetFileContent()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get content: %w", err)
|
||||
}
|
||||
return &FileInfo{
|
||||
Path: filePath,
|
||||
Ref: ref,
|
||||
Data: []byte(data),
|
||||
Hash: content.GetSHA(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *githubRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) {
|
||||
if ref == "" {
|
||||
ref = r.config.Spec.GitHub.Branch
|
||||
}
|
||||
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
tree, truncated, err := r.gh.GetTree(ctx, r.owner, r.repo, r.config.Spec.GitHub.Path, ref, true)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgh.ErrResourceNotFound) {
|
||||
return nil, &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Message: fmt.Sprintf("tree not found; ref=%s", ref),
|
||||
Code: http.StatusNotFound,
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("get tree: %w", err)
|
||||
}
|
||||
|
||||
if truncated {
|
||||
return nil, fmt.Errorf("tree truncated")
|
||||
}
|
||||
|
||||
entries := make([]FileTreeEntry, 0, len(tree))
|
||||
for _, entry := range tree {
|
||||
isBlob := !entry.IsDirectory()
|
||||
// FIXME: this we could potentially do somewhere else on in a different way
|
||||
filePath := entry.GetPath()
|
||||
if !isBlob && !safepath.IsDir(filePath) {
|
||||
filePath = filePath + "/"
|
||||
}
|
||||
|
||||
converted := FileTreeEntry{
|
||||
Path: filePath,
|
||||
Size: entry.GetSize(),
|
||||
Hash: entry.GetSHA(),
|
||||
Blob: !entry.IsDirectory(),
|
||||
}
|
||||
entries = append(entries, converted)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (r *githubRepository) Create(ctx context.Context, path, ref string, data []byte, comment string) error {
|
||||
if ref == "" {
|
||||
ref = r.config.Spec.GitHub.Branch
|
||||
}
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
|
||||
if err := r.ensureBranchExists(ctx, ref); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finalPath := safepath.Join(r.config.Spec.GitHub.Path, path)
|
||||
|
||||
// Create .keep file if it is a directory
|
||||
if safepath.IsDir(finalPath) {
|
||||
if data != nil {
|
||||
return apierrors.NewBadRequest("data cannot be provided for a directory")
|
||||
}
|
||||
|
||||
finalPath = safepath.Join(finalPath, ".keep")
|
||||
data = []byte{}
|
||||
}
|
||||
|
||||
err := r.gh.CreateFile(ctx, r.owner, r.repo, finalPath, ref, comment, data)
|
||||
if errors.Is(err, pgh.ErrResourceAlreadyExists) {
|
||||
return &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Message: "file already exists",
|
||||
Code: http.StatusConflict,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *githubRepository) Update(ctx context.Context, path, ref string, data []byte, comment string) error {
|
||||
if ref == "" {
|
||||
ref = r.config.Spec.GitHub.Branch
|
||||
}
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
|
||||
if err := r.ensureBranchExists(ctx, ref); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finalPath := safepath.Join(r.config.Spec.GitHub.Path, path)
|
||||
file, _, err := r.gh.GetContents(ctx, r.owner, r.repo, finalPath, ref)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgh.ErrResourceNotFound) {
|
||||
return &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Message: "file not found",
|
||||
Code: http.StatusNotFound,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("get content before file update: %w", err)
|
||||
}
|
||||
if file.IsDirectory() {
|
||||
return apierrors.NewBadRequest("cannot update a directory")
|
||||
}
|
||||
|
||||
if err := r.gh.UpdateFile(ctx, r.owner, r.repo, finalPath, ref, comment, file.GetSHA(), data); err != nil {
|
||||
return fmt.Errorf("update file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *githubRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||
if ref == "" {
|
||||
ref = r.config.Spec.GitHub.Branch
|
||||
}
|
||||
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
_, err := r.Read(ctx, path, ref)
|
||||
if err != nil && !(errors.Is(err, ErrFileNotFound)) {
|
||||
return fmt.Errorf("check if file exists before writing: %w", err)
|
||||
}
|
||||
if err == nil {
|
||||
return r.Update(ctx, path, ref, data, message)
|
||||
}
|
||||
|
||||
return r.Create(ctx, path, ref, data, message)
|
||||
}
|
||||
|
||||
func (r *githubRepository) Delete(ctx context.Context, path, ref, comment string) error {
|
||||
if ref == "" {
|
||||
ref = r.config.Spec.GitHub.Branch
|
||||
}
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
|
||||
if err := r.ensureBranchExists(ctx, ref); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: should add some protection against deleting the root directory?
|
||||
|
||||
// Inside deleteRecursively, all paths are relative to the root of the repository
|
||||
// so we need to prepend the prefix there but only here.
|
||||
finalPath := safepath.Join(r.config.Spec.GitHub.Path, path)
|
||||
|
||||
return r.deleteRecursively(ctx, finalPath, ref, comment)
|
||||
}
|
||||
|
||||
func (r *githubRepository) deleteRecursively(ctx context.Context, path, ref, comment string) error {
|
||||
file, contents, err := r.gh.GetContents(ctx, r.owner, r.repo, path, ref)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgh.ErrResourceNotFound) {
|
||||
return ErrFileNotFound
|
||||
}
|
||||
|
||||
return fmt.Errorf("find file to delete: %w", err)
|
||||
}
|
||||
|
||||
if file != nil && !file.IsDirectory() {
|
||||
return r.gh.DeleteFile(ctx, r.owner, r.repo, path, ref, comment, file.GetSHA())
|
||||
}
|
||||
|
||||
for _, c := range contents {
|
||||
p := c.GetPath()
|
||||
if c.IsDirectory() {
|
||||
if err := r.deleteRecursively(ctx, p, ref, comment); err != nil {
|
||||
return fmt.Errorf("delete directory recursively: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := r.gh.DeleteFile(ctx, r.owner, r.repo, p, ref, comment, c.GetSHA()); err != nil {
|
||||
return fmt.Errorf("delete file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *githubRepository) History(ctx context.Context, path, ref string) ([]provisioning.HistoryItem, error) {
|
||||
if ref == "" {
|
||||
ref = r.config.Spec.GitHub.Branch
|
||||
}
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
|
||||
finalPath := safepath.Join(r.config.Spec.GitHub.Path, path)
|
||||
commits, err := r.gh.Commits(ctx, r.owner, r.repo, finalPath, ref)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgh.ErrResourceNotFound) {
|
||||
return nil, ErrFileNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("get commits: %w", err)
|
||||
}
|
||||
|
||||
ret := make([]provisioning.HistoryItem, 0, len(commits))
|
||||
for _, commit := range commits {
|
||||
authors := make([]provisioning.Author, 0)
|
||||
if commit.Author != nil {
|
||||
authors = append(authors, provisioning.Author{
|
||||
Name: commit.Author.Name,
|
||||
Username: commit.Author.Username,
|
||||
AvatarURL: commit.Author.AvatarURL,
|
||||
})
|
||||
}
|
||||
|
||||
if commit.Committer != nil && commit.Author != nil && commit.Author.Name != commit.Committer.Name {
|
||||
authors = append(authors, provisioning.Author{
|
||||
Name: commit.Committer.Name,
|
||||
Username: commit.Committer.Username,
|
||||
AvatarURL: commit.Committer.AvatarURL,
|
||||
})
|
||||
}
|
||||
|
||||
ret = append(ret, provisioning.HistoryItem{
|
||||
Ref: commit.Ref,
|
||||
Message: commit.Message,
|
||||
Authors: authors,
|
||||
CreatedAt: commit.CreatedAt.UnixMilli(),
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// basicGitBranchNameRegex is a regular expression to validate a git branch name
|
||||
// it does not cover all cases as positive lookaheads are not supported in Go's regexp
|
||||
var basicGitBranchNameRegex = regexp.MustCompile(`^[a-zA-Z0-9\-\_\/\.]+$`)
|
||||
|
||||
// IsValidGitBranchName checks if a branch name is valid.
|
||||
// It uses the following regexp `^[a-zA-Z0-9\-\_\/\.]+$` to validate the branch name with some additional checks that must satisfy the following rules:
|
||||
// 1. The branch name must have at least one character and must not be empty.
|
||||
// 2. The branch name cannot start with `/` or end with `/`, `.`, or whitespace.
|
||||
// 3. The branch name cannot contain consecutive slashes (`//`).
|
||||
// 4. The branch name cannot contain consecutive dots (`..`).
|
||||
// 5. The branch name cannot contain `@{`.
|
||||
// 6. The branch name cannot include the following characters: `~`, `^`, `:`, `?`, `*`, `[`, `\`, or `]`.
|
||||
func IsValidGitBranchName(branch string) bool {
|
||||
if !basicGitBranchNameRegex.MatchString(branch) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Additional checks for invalid patterns
|
||||
if strings.HasPrefix(branch, "/") || strings.HasSuffix(branch, "/") ||
|
||||
strings.HasSuffix(branch, ".") || strings.Contains(branch, "..") ||
|
||||
strings.Contains(branch, "//") || strings.HasSuffix(branch, ".lock") {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *githubRepository) ensureBranchExists(ctx context.Context, branchName string) error {
|
||||
if !IsValidGitBranchName(branchName) {
|
||||
return &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: "invalid branch name",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
ok, err := r.gh.BranchExists(ctx, r.owner, r.repo, branchName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check branch exists: %w", err)
|
||||
}
|
||||
|
||||
if ok {
|
||||
logging.FromContext(ctx).Info("branch already exists", "branch", branchName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
srcBranch := r.config.Spec.GitHub.Branch
|
||||
if err := r.gh.CreateBranch(ctx, r.owner, r.repo, srcBranch, branchName); err != nil {
|
||||
if errors.Is(err, pgh.ErrResourceAlreadyExists) {
|
||||
return &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Code: http.StatusConflict,
|
||||
Message: "branch already exists",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("create branch: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *githubRepository) LatestRef(ctx context.Context) (string, error) {
|
||||
ctx, _ = r.logger(ctx, "")
|
||||
branch, err := r.gh.GetBranch(ctx, r.owner, r.repo, r.Config().Spec.GitHub.Branch)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get branch: %w", err)
|
||||
}
|
||||
|
||||
return branch.Sha, nil
|
||||
}
|
||||
|
||||
func (r *githubRepository) CompareFiles(ctx context.Context, base, ref string) ([]VersionedFileChange, error) {
|
||||
if ref == "" {
|
||||
var err error
|
||||
ref, err = r.LatestRef(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get latest ref: %w", err)
|
||||
}
|
||||
}
|
||||
ctx, logger := r.logger(ctx, ref)
|
||||
|
||||
files, err := r.gh.CompareCommits(ctx, r.owner, r.repo, base, ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("compare commits: %w", err)
|
||||
}
|
||||
|
||||
changes := make([]VersionedFileChange, 0)
|
||||
for _, f := range files {
|
||||
// reference: https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#get-a-commit
|
||||
switch f.GetStatus() {
|
||||
case "added", "copied":
|
||||
currentPath, err := safepath.RelativeTo(f.GetFilename(), r.config.Spec.GitHub.Path)
|
||||
if err != nil {
|
||||
// do nothing as it's outside of configured path
|
||||
continue
|
||||
}
|
||||
|
||||
changes = append(changes, VersionedFileChange{
|
||||
Path: currentPath,
|
||||
Ref: ref,
|
||||
Action: FileActionCreated,
|
||||
})
|
||||
case "modified", "changed":
|
||||
currentPath, err := safepath.RelativeTo(f.GetFilename(), r.config.Spec.GitHub.Path)
|
||||
if err != nil {
|
||||
// do nothing as it's outside of configured path
|
||||
continue
|
||||
}
|
||||
|
||||
changes = append(changes, VersionedFileChange{
|
||||
Path: currentPath,
|
||||
Ref: ref,
|
||||
Action: FileActionUpdated,
|
||||
})
|
||||
case "renamed":
|
||||
previousPath, previousErr := safepath.RelativeTo(f.GetPreviousFilename(), r.config.Spec.GitHub.Path)
|
||||
currentPath, currentErr := safepath.RelativeTo(f.GetFilename(), r.config.Spec.GitHub.Path)
|
||||
|
||||
// Handle all possible combinations of path validation results:
|
||||
// 1. Both paths outside configured path, do nothing
|
||||
// 2. Both paths inside configured path, rename
|
||||
// 3. Moving out of configured path, delete previous file
|
||||
// 4. Moving into configured path, create new file
|
||||
switch {
|
||||
case previousErr != nil && currentErr != nil:
|
||||
// do nothing as it's outside of configured path
|
||||
case previousErr == nil && currentErr == nil:
|
||||
changes = append(changes, VersionedFileChange{
|
||||
Path: currentPath,
|
||||
PreviousPath: previousPath,
|
||||
Ref: ref,
|
||||
PreviousRef: base,
|
||||
Action: FileActionRenamed,
|
||||
})
|
||||
case previousErr == nil && currentErr != nil:
|
||||
changes = append(changes, VersionedFileChange{
|
||||
Path: previousPath,
|
||||
Ref: base,
|
||||
Action: FileActionDeleted,
|
||||
})
|
||||
case previousErr != nil && currentErr == nil:
|
||||
changes = append(changes, VersionedFileChange{
|
||||
Path: currentPath,
|
||||
Ref: ref,
|
||||
Action: FileActionCreated,
|
||||
})
|
||||
}
|
||||
case "removed":
|
||||
currentPath, err := safepath.RelativeTo(f.GetFilename(), r.config.Spec.GitHub.Path)
|
||||
if err != nil {
|
||||
// do nothing as it's outside of configured path
|
||||
continue
|
||||
}
|
||||
|
||||
changes = append(changes, VersionedFileChange{
|
||||
Ref: ref,
|
||||
PreviousRef: base,
|
||||
Path: currentPath,
|
||||
PreviousPath: currentPath,
|
||||
Action: FileActionDeleted,
|
||||
})
|
||||
case "unchanged":
|
||||
// do nothing
|
||||
default:
|
||||
logger.Error("ignore unhandled file", "file", f.GetFilename(), "status", f.GetStatus())
|
||||
}
|
||||
}
|
||||
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
// ResourceURLs implements RepositoryWithURLs.
|
||||
func (r *githubRepository) ResourceURLs(ctx context.Context, file *FileInfo) (*provisioning.ResourceURLs, error) {
|
||||
cfg := r.config.Spec.GitHub
|
||||
if file.Path == "" || cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ref := file.Ref
|
||||
if ref == "" {
|
||||
ref = cfg.Branch
|
||||
}
|
||||
|
||||
urls := &provisioning.ResourceURLs{
|
||||
RepositoryURL: cfg.URL,
|
||||
SourceURL: fmt.Sprintf("%s/blob/%s/%s", cfg.URL, ref, file.Path),
|
||||
}
|
||||
|
||||
if ref != cfg.Branch {
|
||||
urls.CompareURL = fmt.Sprintf("%s/compare/%s...%s", cfg.URL, cfg.Branch, ref)
|
||||
|
||||
// Create a new pull request
|
||||
urls.NewPullRequestURL = fmt.Sprintf("%s?quick_pull=1&labels=grafana", urls.CompareURL)
|
||||
}
|
||||
|
||||
return urls, nil
|
||||
}
|
||||
|
||||
func (r *githubRepository) Clone(ctx context.Context, opts CloneOptions) (ClonedRepository, error) {
|
||||
return r.cloneFn(ctx, opts)
|
||||
}
|
||||
|
||||
func (r *githubRepository) logger(ctx context.Context, ref string) (context.Context, logging.Logger) {
|
||||
logger := logging.FromContext(ctx)
|
||||
|
||||
type containsGh int
|
||||
var containsGhKey containsGh
|
||||
if ctx.Value(containsGhKey) != nil {
|
||||
return ctx, logging.FromContext(ctx)
|
||||
}
|
||||
|
||||
if ref == "" {
|
||||
ref = r.config.Spec.GitHub.Branch
|
||||
}
|
||||
logger = logger.With(slog.Group("github_repository", "owner", r.owner, "name", r.repo, "ref", ref))
|
||||
ctx = logging.Context(ctx, logger)
|
||||
// We want to ensure we don't add multiple github_repository keys. With doesn't deduplicate the keys...
|
||||
ctx = context.WithValue(ctx, containsGhKey, true)
|
||||
return ctx, logger
|
||||
}
|
||||
@@ -7,127 +7,34 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v70/github"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
|
||||
// API errors that we need to convey after parsing real GH errors (or faking them).
|
||||
var (
|
||||
ErrResourceAlreadyExists = errors.New("the resource already exists")
|
||||
ErrResourceNotFound = errors.New("the resource does not exist")
|
||||
ErrMismatchedHash = errors.New("the update cannot be applied because the expected and actual hashes are unequal")
|
||||
ErrNoSecret = errors.New("new webhooks must have a secret")
|
||||
ErrResourceNotFound = errors.New("the resource does not exist")
|
||||
//lint:ignore ST1005 this is not punctuation
|
||||
ErrPathTraversalDisallowed = errors.New("the path contained ..") //nolint:staticcheck
|
||||
ErrServiceUnavailable = apierrors.NewServiceUnavailable("github is unavailable")
|
||||
ErrFileTooLarge = errors.New("file exceeds maximum allowed size")
|
||||
ErrTooManyItems = errors.New("maximum number of items exceeded")
|
||||
ErrServiceUnavailable = apierrors.NewServiceUnavailable("github is unavailable")
|
||||
ErrTooManyItems = errors.New("maximum number of items exceeded")
|
||||
)
|
||||
|
||||
// MaxFileSize maximum file size limit (10MB)
|
||||
const MaxFileSize = 10 * 1024 * 1024 // 10MB in bytes
|
||||
|
||||
type ErrRateLimited = github.RateLimitError
|
||||
|
||||
//go:generate mockery --name Client --structname MockClient --inpackage --filename mock_client.go --with-expecter
|
||||
type Client interface {
|
||||
// IsAuthenticated checks if the client is authenticated.
|
||||
IsAuthenticated(ctx context.Context) error
|
||||
|
||||
// GetContents returns the metadata and content of a file or directory.
|
||||
// When a file is checked, the first returned value will have a value. For a directory, the second will. The other value is always nil.
|
||||
// If an error occurs, the returned values may or may not be nil.
|
||||
//
|
||||
// If ".." appears in the "path", this method will return an error.
|
||||
GetContents(ctx context.Context, owner, repository, path, ref string) (fileContents RepositoryContent, dirContents []RepositoryContent, err error)
|
||||
|
||||
// GetTree returns the Git tree in the repository.
|
||||
// When recursive is given, subtrees are mapped into the returned array.
|
||||
// When basePath is given, only trees under it are given. The results do not include this path in their names.
|
||||
//
|
||||
// The truncated bool will be set to true if the tree is larger than 7 MB or 100 000 entries.
|
||||
// When truncated is true, you may wish to read each subtree manually instead.
|
||||
GetTree(ctx context.Context, owner, repository, basePath, ref string, recursive bool) (entries []RepositoryContent, truncated bool, err error)
|
||||
|
||||
// CreateFile creates a new file in the repository under the given path.
|
||||
// The file is created on the branch given.
|
||||
// The message given is the commit message. If none is given, an appropriate default is used.
|
||||
// The content is what the file should contain. An empty slice is valid, though often not very useful.
|
||||
//
|
||||
// If ".." appears in the "path", this method will return an error.
|
||||
CreateFile(ctx context.Context, owner, repository, path, branch, message string, content []byte) error
|
||||
|
||||
// UpdateFile updates a file in the repository under the given path.
|
||||
// The file is updated on the branch given.
|
||||
// The message given is the commit message. If none is given, an appropriate default is used.
|
||||
// The content is what the file should contain. An empty slice is valid, though often not very useful.
|
||||
// If the path does not exist, an error is returned.
|
||||
// The hash given must be the SHA hash of the file contents. Calling GetContents in advance is an easy way of handling this.
|
||||
//
|
||||
// If ".." appears in the "path", this method will return an error.
|
||||
UpdateFile(ctx context.Context, owner, repository, path, branch, message, hash string, content []byte) error
|
||||
|
||||
// DeleteFile deletes a file in the repository under the given path.
|
||||
// The file is deleted from the branch given.
|
||||
// The message given is the commit message. If none is given, an appropriate default is used.
|
||||
// If the path does not exist, an error is returned.
|
||||
// The hash given must be the SHA hash of the file contents. Calling GetContents in advance is an easy way of handling this.
|
||||
//
|
||||
// If ".." appears in the "path", this method will return an error.
|
||||
DeleteFile(ctx context.Context, owner, repository, path, branch, message, hash string) error
|
||||
|
||||
// Commits returns the commits for the given path
|
||||
// Commits
|
||||
Commits(ctx context.Context, owner, repository, path, branch string) ([]Commit, error)
|
||||
|
||||
// CompareCommits returns the changes between two commits.
|
||||
CompareCommits(ctx context.Context, owner, repository, base, head string) ([]CommitFile, error)
|
||||
|
||||
// RepoExists checks if a repository exists.
|
||||
RepoExists(ctx context.Context, owner, repository string) (bool, error)
|
||||
|
||||
// CreateBranch creates a new branch in the repository.
|
||||
CreateBranch(ctx context.Context, owner, repository, sourceBranch, branchName string) error
|
||||
// BranchExists checks if a branch exists in the repository.
|
||||
BranchExists(ctx context.Context, owner, repository, branchName string) (bool, error)
|
||||
// GetBranch returns the branch of the repository.
|
||||
GetBranch(ctx context.Context, owner, repository, branchName string) (Branch, error)
|
||||
|
||||
// Webhooks
|
||||
ListWebhooks(ctx context.Context, owner, repository string) ([]WebhookConfig, error)
|
||||
CreateWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) (WebhookConfig, error)
|
||||
GetWebhook(ctx context.Context, owner, repository string, webhookID int64) (WebhookConfig, error)
|
||||
DeleteWebhook(ctx context.Context, owner, repository string, webhookID int64) error
|
||||
EditWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) error
|
||||
|
||||
// Pull requests
|
||||
ListPullRequestFiles(ctx context.Context, owner, repository string, number int) ([]CommitFile, error)
|
||||
CreatePullRequestComment(ctx context.Context, owner, repository string, number int, body string) error
|
||||
}
|
||||
|
||||
//go:generate mockery --name RepositoryContent --structname MockRepositoryContent --inpackage --filename mock_repository_content.go --with-expecter
|
||||
type RepositoryContent interface {
|
||||
// Returns true if this is a directory, false if it is a file.
|
||||
IsDirectory() bool
|
||||
// Returns the contents of the file. Decoding happens if necessary.
|
||||
// Returns an error if the content represents a directory.
|
||||
GetFileContent() (string, error)
|
||||
// Returns true if this is a symlink.
|
||||
// If true, GetPath returns the path where this symlink leads.
|
||||
IsSymlink() bool
|
||||
// Returns the full path from the root of the repository.
|
||||
// This has no leading or trailing slashes.
|
||||
// The path only uses '/' for directories. You can use the 'path' package to interact with these.
|
||||
GetPath() string
|
||||
// Get the SHA hash. This is usually a SHA-256, but may also be SHA-512.
|
||||
// Directories have SHA hashes, too (TODO: how is this calculated?).
|
||||
GetSHA() string
|
||||
// The size of the file. Not necessarily non-zero, even if the file is supposed to be non-zero.
|
||||
GetSize() int64
|
||||
}
|
||||
|
||||
type Branch struct {
|
||||
Name string
|
||||
Sha string
|
||||
}
|
||||
|
||||
type CommitAuthor struct {
|
||||
Name string
|
||||
Username string
|
||||
@@ -150,20 +57,6 @@ type CommitFile interface {
|
||||
GetStatus() string
|
||||
}
|
||||
|
||||
type FileComment struct {
|
||||
Content string
|
||||
Path string
|
||||
Position int
|
||||
Ref string
|
||||
}
|
||||
|
||||
type CreateFileOptions struct {
|
||||
// The message of the commit. May be empty, in which case a default value is entered.
|
||||
Message string
|
||||
// The content of the file to write, unencoded.
|
||||
Content []byte
|
||||
}
|
||||
|
||||
type WebhookConfig struct {
|
||||
// The ID of the webhook.
|
||||
// Can be 0 on creation.
|
||||
|
||||
@@ -27,6 +27,11 @@ func (r *Factory) New(ctx context.Context, ghToken string) Client {
|
||||
tokenSrc := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: ghToken},
|
||||
)
|
||||
tokenClient := oauth2.NewClient(ctx, tokenSrc)
|
||||
return NewClient(github.NewClient(tokenClient))
|
||||
|
||||
if len(ghToken) == 0 {
|
||||
tokenClient := oauth2.NewClient(ctx, tokenSrc)
|
||||
return NewClient(github.NewClient(tokenClient))
|
||||
}
|
||||
|
||||
return NewClient(github.NewClient(&http.Client{}))
|
||||
}
|
||||
|
||||
+98
-98
@@ -1,14 +1,14 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package repository
|
||||
package github
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
github "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
@@ -27,19 +27,19 @@ func (_m *MockGithubRepository) EXPECT() *MockGithubRepository_Expecter {
|
||||
}
|
||||
|
||||
// Client provides a mock function with no fields
|
||||
func (_m *MockGithubRepository) Client() github.Client {
|
||||
func (_m *MockGithubRepository) Client() Client {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Client")
|
||||
}
|
||||
|
||||
var r0 github.Client
|
||||
if rf, ok := ret.Get(0).(func() github.Client); ok {
|
||||
var r0 Client
|
||||
if rf, ok := ret.Get(0).(func() Client); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(github.Client)
|
||||
r0 = ret.Get(0).(Client)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,93 +63,34 @@ func (_c *MockGithubRepository_Client_Call) Run(run func()) *MockGithubRepositor
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Client_Call) Return(_a0 github.Client) *MockGithubRepository_Client_Call {
|
||||
func (_c *MockGithubRepository_Client_Call) Return(_a0 Client) *MockGithubRepository_Client_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Client_Call) RunAndReturn(run func() github.Client) *MockGithubRepository_Client_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Clone provides a mock function with given fields: ctx, opts
|
||||
func (_m *MockGithubRepository) Clone(ctx context.Context, opts CloneOptions) (ClonedRepository, error) {
|
||||
ret := _m.Called(ctx, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Clone")
|
||||
}
|
||||
|
||||
var r0 ClonedRepository
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) (ClonedRepository, error)); ok {
|
||||
return rf(ctx, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) ClonedRepository); ok {
|
||||
r0 = rf(ctx, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(ClonedRepository)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, CloneOptions) error); ok {
|
||||
r1 = rf(ctx, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGithubRepository_Clone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clone'
|
||||
type MockGithubRepository_Clone_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Clone is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - opts CloneOptions
|
||||
func (_e *MockGithubRepository_Expecter) Clone(ctx interface{}, opts interface{}) *MockGithubRepository_Clone_Call {
|
||||
return &MockGithubRepository_Clone_Call{Call: _e.mock.On("Clone", ctx, opts)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Clone_Call) Run(run func(ctx context.Context, opts CloneOptions)) *MockGithubRepository_Clone_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(CloneOptions))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Clone_Call) Return(_a0 ClonedRepository, _a1 error) *MockGithubRepository_Clone_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Clone_Call) RunAndReturn(run func(context.Context, CloneOptions) (ClonedRepository, error)) *MockGithubRepository_Clone_Call {
|
||||
func (_c *MockGithubRepository_Client_Call) RunAndReturn(run func() Client) *MockGithubRepository_Client_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CompareFiles provides a mock function with given fields: ctx, base, ref
|
||||
func (_m *MockGithubRepository) CompareFiles(ctx context.Context, base string, ref string) ([]VersionedFileChange, error) {
|
||||
func (_m *MockGithubRepository) CompareFiles(ctx context.Context, base string, ref string) ([]repository.VersionedFileChange, error) {
|
||||
ret := _m.Called(ctx, base, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CompareFiles")
|
||||
}
|
||||
|
||||
var r0 []VersionedFileChange
|
||||
var r0 []repository.VersionedFileChange
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]VersionedFileChange, error)); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]repository.VersionedFileChange, error)); ok {
|
||||
return rf(ctx, base, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []VersionedFileChange); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []repository.VersionedFileChange); ok {
|
||||
r0 = rf(ctx, base, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]VersionedFileChange)
|
||||
r0 = ret.Get(0).([]repository.VersionedFileChange)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,12 +123,12 @@ func (_c *MockGithubRepository_CompareFiles_Call) Run(run func(ctx context.Conte
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_CompareFiles_Call) Return(_a0 []VersionedFileChange, _a1 error) *MockGithubRepository_CompareFiles_Call {
|
||||
func (_c *MockGithubRepository_CompareFiles_Call) Return(_a0 []repository.VersionedFileChange, _a1 error) *MockGithubRepository_CompareFiles_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_CompareFiles_Call) RunAndReturn(run func(context.Context, string, string) ([]VersionedFileChange, error)) *MockGithubRepository_CompareFiles_Call {
|
||||
func (_c *MockGithubRepository_CompareFiles_Call) RunAndReturn(run func(context.Context, string, string) ([]repository.VersionedFileChange, error)) *MockGithubRepository_CompareFiles_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -500,23 +441,23 @@ func (_c *MockGithubRepository_Owner_Call) RunAndReturn(run func() string) *Mock
|
||||
}
|
||||
|
||||
// Read provides a mock function with given fields: ctx, path, ref
|
||||
func (_m *MockGithubRepository) Read(ctx context.Context, path string, ref string) (*FileInfo, error) {
|
||||
func (_m *MockGithubRepository) Read(ctx context.Context, path string, ref string) (*repository.FileInfo, error) {
|
||||
ret := _m.Called(ctx, path, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Read")
|
||||
}
|
||||
|
||||
var r0 *FileInfo
|
||||
var r0 *repository.FileInfo
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*FileInfo, error)); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*repository.FileInfo, error)); ok {
|
||||
return rf(ctx, path, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) *FileInfo); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) *repository.FileInfo); ok {
|
||||
r0 = rf(ctx, path, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*FileInfo)
|
||||
r0 = ret.Get(0).(*repository.FileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,34 +490,34 @@ func (_c *MockGithubRepository_Read_Call) Run(run func(ctx context.Context, path
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Read_Call) Return(_a0 *FileInfo, _a1 error) *MockGithubRepository_Read_Call {
|
||||
func (_c *MockGithubRepository_Read_Call) Return(_a0 *repository.FileInfo, _a1 error) *MockGithubRepository_Read_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*FileInfo, error)) *MockGithubRepository_Read_Call {
|
||||
func (_c *MockGithubRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*repository.FileInfo, error)) *MockGithubRepository_Read_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ReadTree provides a mock function with given fields: ctx, ref
|
||||
func (_m *MockGithubRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) {
|
||||
func (_m *MockGithubRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
||||
ret := _m.Called(ctx, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ReadTree")
|
||||
}
|
||||
|
||||
var r0 []FileTreeEntry
|
||||
var r0 []repository.FileTreeEntry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) ([]FileTreeEntry, error)); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) ([]repository.FileTreeEntry, error)); ok {
|
||||
return rf(ctx, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) []FileTreeEntry); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) []repository.FileTreeEntry); ok {
|
||||
r0 = rf(ctx, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]FileTreeEntry)
|
||||
r0 = ret.Get(0).([]repository.FileTreeEntry)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,12 +549,12 @@ func (_c *MockGithubRepository_ReadTree_Call) Run(run func(ctx context.Context,
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_ReadTree_Call) Return(_a0 []FileTreeEntry, _a1 error) *MockGithubRepository_ReadTree_Call {
|
||||
func (_c *MockGithubRepository_ReadTree_Call) Return(_a0 []repository.FileTreeEntry, _a1 error) *MockGithubRepository_ReadTree_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]FileTreeEntry, error)) *MockGithubRepository_ReadTree_Call {
|
||||
func (_c *MockGithubRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]repository.FileTreeEntry, error)) *MockGithubRepository_ReadTree_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -664,7 +605,7 @@ func (_c *MockGithubRepository_Repo_Call) RunAndReturn(run func() string) *MockG
|
||||
}
|
||||
|
||||
// ResourceURLs provides a mock function with given fields: ctx, file
|
||||
func (_m *MockGithubRepository) ResourceURLs(ctx context.Context, file *FileInfo) (*v0alpha1.ResourceURLs, error) {
|
||||
func (_m *MockGithubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*v0alpha1.ResourceURLs, error) {
|
||||
ret := _m.Called(ctx, file)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -673,10 +614,10 @@ func (_m *MockGithubRepository) ResourceURLs(ctx context.Context, file *FileInfo
|
||||
|
||||
var r0 *v0alpha1.ResourceURLs
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *FileInfo) (*v0alpha1.ResourceURLs, error)); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *repository.FileInfo) (*v0alpha1.ResourceURLs, error)); ok {
|
||||
return rf(ctx, file)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *FileInfo) *v0alpha1.ResourceURLs); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *repository.FileInfo) *v0alpha1.ResourceURLs); ok {
|
||||
r0 = rf(ctx, file)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
@@ -684,7 +625,7 @@ func (_m *MockGithubRepository) ResourceURLs(ctx context.Context, file *FileInfo
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *FileInfo) error); ok {
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *repository.FileInfo) error); ok {
|
||||
r1 = rf(ctx, file)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
@@ -700,14 +641,14 @@ type MockGithubRepository_ResourceURLs_Call struct {
|
||||
|
||||
// ResourceURLs is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - file *FileInfo
|
||||
// - file *repository.FileInfo
|
||||
func (_e *MockGithubRepository_Expecter) ResourceURLs(ctx interface{}, file interface{}) *MockGithubRepository_ResourceURLs_Call {
|
||||
return &MockGithubRepository_ResourceURLs_Call{Call: _e.mock.On("ResourceURLs", ctx, file)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_ResourceURLs_Call) Run(run func(ctx context.Context, file *FileInfo)) *MockGithubRepository_ResourceURLs_Call {
|
||||
func (_c *MockGithubRepository_ResourceURLs_Call) Run(run func(ctx context.Context, file *repository.FileInfo)) *MockGithubRepository_ResourceURLs_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(*FileInfo))
|
||||
run(args[0].(context.Context), args[1].(*repository.FileInfo))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -717,7 +658,66 @@ func (_c *MockGithubRepository_ResourceURLs_Call) Return(_a0 *v0alpha1.ResourceU
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_ResourceURLs_Call) RunAndReturn(run func(context.Context, *FileInfo) (*v0alpha1.ResourceURLs, error)) *MockGithubRepository_ResourceURLs_Call {
|
||||
func (_c *MockGithubRepository_ResourceURLs_Call) RunAndReturn(run func(context.Context, *repository.FileInfo) (*v0alpha1.ResourceURLs, error)) *MockGithubRepository_ResourceURLs_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Stage provides a mock function with given fields: ctx, opts
|
||||
func (_m *MockGithubRepository) Stage(ctx context.Context, opts repository.StageOptions) (repository.StagedRepository, error) {
|
||||
ret := _m.Called(ctx, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Stage")
|
||||
}
|
||||
|
||||
var r0 repository.StagedRepository
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, repository.StageOptions) (repository.StagedRepository, error)); ok {
|
||||
return rf(ctx, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, repository.StageOptions) repository.StagedRepository); ok {
|
||||
r0 = rf(ctx, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(repository.StagedRepository)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, repository.StageOptions) error); ok {
|
||||
r1 = rf(ctx, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGithubRepository_Stage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stage'
|
||||
type MockGithubRepository_Stage_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Stage is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - opts repository.StageOptions
|
||||
func (_e *MockGithubRepository_Expecter) Stage(ctx interface{}, opts interface{}) *MockGithubRepository_Stage_Call {
|
||||
return &MockGithubRepository_Stage_Call{Call: _e.mock.On("Stage", ctx, opts)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Stage_Call) Run(run func(ctx context.Context, opts repository.StageOptions)) *MockGithubRepository_Stage_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(repository.StageOptions))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Stage_Call) Return(_a0 repository.StagedRepository, _a1 error) *MockGithubRepository_Stage_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Stage_Call) RunAndReturn(run func(context.Context, repository.StageOptions) (repository.StagedRepository, error)) *MockGithubRepository_Stage_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -8,10 +8,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v70/github"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||
)
|
||||
|
||||
type githubClient struct {
|
||||
@@ -22,268 +18,12 @@ func NewClient(client *github.Client) Client {
|
||||
return &githubClient{client}
|
||||
}
|
||||
|
||||
func (r *githubClient) IsAuthenticated(ctx context.Context) error {
|
||||
if _, _, err := r.gh.Users.Get(ctx, ""); err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) {
|
||||
switch ghErr.Response.StatusCode {
|
||||
case http.StatusUnauthorized:
|
||||
return apierrors.NewUnauthorized("token is invalid or expired")
|
||||
case http.StatusForbidden:
|
||||
return &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusUnauthorized,
|
||||
Reason: metav1.StatusReasonUnauthorized,
|
||||
Message: "token is revoked or has insufficient permissions",
|
||||
},
|
||||
}
|
||||
case http.StatusServiceUnavailable:
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *githubClient) RepoExists(ctx context.Context, owner, repository string) (bool, error) {
|
||||
_, resp, err := r.gh.Repositories.Get(ctx, owner, repository)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
const (
|
||||
maxDirectoryItems = 1000 // Maximum number of items allowed in a directory
|
||||
maxTreeItems = 10000 // Maximum number of items allowed in a tree
|
||||
maxCommits = 1000 // Maximum number of commits to fetch
|
||||
maxCompareFiles = 1000 // Maximum number of files to compare between commits
|
||||
maxWebhooks = 100 // Maximum number of webhooks allowed per repository
|
||||
maxPRFiles = 1000 // Maximum number of files allowed in a pull request
|
||||
maxPullRequestsFileComments = 1000 // Maximum number of comments allowed in a pull request
|
||||
maxFileSize = 10 * 1024 * 1024 // 10MB in bytes
|
||||
maxCommits = 1000 // Maximum number of commits to fetch
|
||||
maxWebhooks = 100 // Maximum number of webhooks allowed per repository
|
||||
maxPRFiles = 1000 // Maximum number of files allowed in a pull request
|
||||
)
|
||||
|
||||
func (r *githubClient) GetContents(ctx context.Context, owner, repository, path, ref string) (fileContents RepositoryContent, dirContents []RepositoryContent, err error) {
|
||||
// First try to get repository contents
|
||||
opts := &github.RepositoryContentGetOptions{
|
||||
Ref: ref,
|
||||
}
|
||||
|
||||
fc, dc, _, err := r.gh.Repositories.GetContents(ctx, owner, repository, path, opts)
|
||||
if err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return nil, nil, ErrServiceUnavailable
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return nil, nil, ErrResourceNotFound
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if fc != nil {
|
||||
// Check file size before returning content
|
||||
if fc.GetSize() > maxFileSize {
|
||||
return nil, nil, ErrFileTooLarge
|
||||
}
|
||||
return realRepositoryContent{fc}, nil, nil
|
||||
}
|
||||
|
||||
// For directories, check size limits
|
||||
if len(dc) > maxDirectoryItems {
|
||||
return nil, nil, fmt.Errorf("directory contains too many items (more than %d)", maxDirectoryItems)
|
||||
}
|
||||
|
||||
// Convert directory contents
|
||||
allContents := make([]RepositoryContent, 0, len(dc))
|
||||
for _, original := range dc {
|
||||
allContents = append(allContents, realRepositoryContent{original})
|
||||
}
|
||||
|
||||
return nil, allContents, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) GetTree(ctx context.Context, owner, repository, basePath, ref string, recursive bool) ([]RepositoryContent, bool, error) {
|
||||
var tree *github.Tree
|
||||
var err error
|
||||
|
||||
subPaths := safepath.Split(basePath)
|
||||
currentRef := ref
|
||||
|
||||
for {
|
||||
// If subPaths is empty, we can read recursively, as we're reading the tree from the "base" of the repository. Otherwise, always read only the direct children.
|
||||
recursive := recursive && len(subPaths) == 0
|
||||
|
||||
tree, _, err = r.gh.Git.GetTree(ctx, owner, repository, currentRef, recursive)
|
||||
if err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return nil, false, err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return nil, false, ErrServiceUnavailable
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
if currentRef != ref {
|
||||
// We're operating with a subpath which doesn't exist yet.
|
||||
// Pretend as if there is simply no files.
|
||||
// FIXME: why should we pretend this?
|
||||
return nil, false, nil
|
||||
}
|
||||
// currentRef == ref
|
||||
// This indicates the repository or commitish reference doesn't exist. This should always return an error.
|
||||
return nil, false, ErrResourceNotFound
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Check if we've exceeded the maximum allowed items
|
||||
if len(tree.Entries) > maxTreeItems {
|
||||
return nil, false, fmt.Errorf("tree contains too many items (more than %d)", maxTreeItems)
|
||||
}
|
||||
|
||||
// Prep for next iteration.
|
||||
if len(subPaths) == 0 {
|
||||
// We're done: we've discovered the tree we want.
|
||||
break
|
||||
}
|
||||
|
||||
// the ref must be equal the SHA of the entry corresponding to subPaths[0]
|
||||
currentRef = ""
|
||||
for _, e := range tree.Entries {
|
||||
if e.GetPath() == subPaths[0] {
|
||||
currentRef = e.GetSHA()
|
||||
break
|
||||
}
|
||||
}
|
||||
subPaths = subPaths[1:]
|
||||
if currentRef == "" {
|
||||
// We couldn't find the folder in the tree...
|
||||
return nil, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// If the tree is truncated and we're in recursive mode, return an error
|
||||
if tree.GetTruncated() && recursive {
|
||||
return nil, true, fmt.Errorf("tree is too large to fetch recursively (more than %d items)", maxTreeItems)
|
||||
}
|
||||
|
||||
entries := make([]RepositoryContent, 0, len(tree.Entries))
|
||||
for _, te := range tree.Entries {
|
||||
rrc := &realRepositoryContent{
|
||||
real: &github.RepositoryContent{
|
||||
Path: te.Path,
|
||||
Size: te.Size,
|
||||
SHA: te.SHA,
|
||||
},
|
||||
}
|
||||
if te.GetType() == "tree" {
|
||||
rrc.real.Type = github.Ptr("dir")
|
||||
} else {
|
||||
rrc.real.Type = te.Type
|
||||
}
|
||||
entries = append(entries, rrc)
|
||||
}
|
||||
return entries, tree.GetTruncated(), nil
|
||||
}
|
||||
|
||||
func (r *githubClient) CreateFile(ctx context.Context, owner, repository, path, branch, message string, content []byte) error {
|
||||
if message == "" {
|
||||
message = fmt.Sprintf("Create %s", path)
|
||||
}
|
||||
|
||||
_, _, err := r.gh.Repositories.CreateFile(ctx, owner, repository, path, &github.RepositoryContentFileOptions{
|
||||
Branch: &branch,
|
||||
Message: &message,
|
||||
Content: content,
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusUnprocessableEntity {
|
||||
return ErrResourceAlreadyExists
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *githubClient) UpdateFile(ctx context.Context, owner, repository, path, branch, message, hash string, content []byte) error {
|
||||
if message == "" {
|
||||
message = fmt.Sprintf("Update %s", path)
|
||||
}
|
||||
|
||||
_, _, err := r.gh.Repositories.UpdateFile(ctx, owner, repository, path, &github.RepositoryContentFileOptions{
|
||||
Branch: &branch,
|
||||
Message: &message,
|
||||
Content: content,
|
||||
SHA: &hash,
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusConflict {
|
||||
return ErrMismatchedHash
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *githubClient) DeleteFile(ctx context.Context, owner, repository, path, branch, message, hash string) error {
|
||||
if message == "" {
|
||||
message = fmt.Sprintf("Delete %s", path)
|
||||
}
|
||||
|
||||
_, _, err := r.gh.Repositories.DeleteFile(ctx, owner, repository, path, &github.RepositoryContentFileOptions{
|
||||
Branch: &branch,
|
||||
Message: &message,
|
||||
SHA: &hash,
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusConflict {
|
||||
return ErrMismatchedHash
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Commits returns a list of commits for a given repository and branch.
|
||||
func (r *githubClient) Commits(ctx context.Context, owner, repository, path, branch string) ([]Commit, error) {
|
||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.RepositoryCommit, *github.Response, error) {
|
||||
@@ -343,105 +83,6 @@ func (r *githubClient) Commits(ctx context.Context, owner, repository, path, bra
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) CompareCommits(ctx context.Context, owner, repository, base, head string) ([]CommitFile, error) {
|
||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.CommitFile, *github.Response, error) {
|
||||
compare, resp, err := r.gh.Repositories.CompareCommits(ctx, owner, repository, base, head, opts)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return compare.Files, resp, nil
|
||||
}
|
||||
|
||||
files, err := paginatedList(
|
||||
ctx,
|
||||
listFn,
|
||||
defaultListOptions(maxCompareFiles),
|
||||
)
|
||||
if errors.Is(err, ErrTooManyItems) {
|
||||
return nil, fmt.Errorf("too many files changed between commits (more than %d)", maxCompareFiles)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to the interface type
|
||||
ret := make([]CommitFile, 0, len(files))
|
||||
for _, f := range files {
|
||||
ret = append(ret, f)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) GetBranch(ctx context.Context, owner, repository, branchName string) (Branch, error) {
|
||||
branch, resp, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0)
|
||||
if err != nil {
|
||||
// For some reason, GitHub client handles this case differently by failing with a wrapped error
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
return Branch{}, ErrResourceNotFound
|
||||
}
|
||||
|
||||
if resp != nil && resp.StatusCode == http.StatusServiceUnavailable {
|
||||
return Branch{}, ErrServiceUnavailable
|
||||
}
|
||||
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return Branch{}, err
|
||||
}
|
||||
// Leaving these just in case
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return Branch{}, ErrServiceUnavailable
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return Branch{}, ErrResourceNotFound
|
||||
}
|
||||
return Branch{}, err
|
||||
}
|
||||
|
||||
return Branch{
|
||||
Name: branch.GetName(),
|
||||
Sha: branch.GetCommit().GetSHA(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) CreateBranch(ctx context.Context, owner, repository, sourceBranch, branchName string) error {
|
||||
// Fail if the branch already exists
|
||||
if _, _, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0); err == nil {
|
||||
return ErrResourceAlreadyExists
|
||||
}
|
||||
|
||||
// Branch out based on the repository branch
|
||||
baseRef, _, err := r.gh.Repositories.GetBranch(ctx, owner, repository, sourceBranch, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get base branch: %w", err)
|
||||
}
|
||||
|
||||
if _, _, err := r.gh.Git.CreateRef(ctx, owner, repository, &github.Reference{
|
||||
Ref: github.Ptr(fmt.Sprintf("refs/heads/%s", branchName)),
|
||||
Object: &github.GitObject{
|
||||
SHA: baseRef.Commit.SHA,
|
||||
},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("create branch ref: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *githubClient) BranchExists(ctx context.Context, owner, repository, branchName string) (bool, error) {
|
||||
_, resp, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (r *githubClient) ListWebhooks(ctx context.Context, owner, repository string) ([]WebhookConfig, error) {
|
||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
|
||||
return r.gh.Repositories.ListHooks(ctx, owner, repository, opts)
|
||||
@@ -626,44 +267,6 @@ func (r *githubClient) CreatePullRequestComment(ctx context.Context, owner, repo
|
||||
return nil
|
||||
}
|
||||
|
||||
type realRepositoryContent struct {
|
||||
real *github.RepositoryContent
|
||||
}
|
||||
|
||||
var _ RepositoryContent = realRepositoryContent{}
|
||||
|
||||
func (c realRepositoryContent) IsDirectory() bool {
|
||||
return c.real.GetType() == "dir"
|
||||
}
|
||||
|
||||
func (c realRepositoryContent) GetFileContent() (string, error) {
|
||||
return c.real.GetContent()
|
||||
}
|
||||
|
||||
func (c realRepositoryContent) IsSymlink() bool {
|
||||
return c.real.Target != nil
|
||||
}
|
||||
|
||||
func (c realRepositoryContent) GetPath() string {
|
||||
return c.real.GetPath()
|
||||
}
|
||||
|
||||
func (c realRepositoryContent) GetSHA() string {
|
||||
return c.real.GetSHA()
|
||||
}
|
||||
|
||||
func (c realRepositoryContent) GetSize() int64 {
|
||||
if c.real.Size != nil {
|
||||
return int64(*c.real.Size)
|
||||
}
|
||||
if c.real.Content != nil {
|
||||
if c, err := c.real.GetContent(); err == nil {
|
||||
return int64(len(c))
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// listOptions represents pagination parameters for list operations
|
||||
type listOptions struct {
|
||||
github.ListOptions
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package github
|
||||
|
||||
@@ -21,65 +21,6 @@ func (_m *MockClient) EXPECT() *MockClient_Expecter {
|
||||
return &MockClient_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// BranchExists provides a mock function with given fields: ctx, owner, repository, branchName
|
||||
func (_m *MockClient) BranchExists(ctx context.Context, owner string, repository string, branchName string) (bool, error) {
|
||||
ret := _m.Called(ctx, owner, repository, branchName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for BranchExists")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (bool, error)); ok {
|
||||
return rf(ctx, owner, repository, branchName)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) bool); ok {
|
||||
r0 = rf(ctx, owner, repository, branchName)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
|
||||
r1 = rf(ctx, owner, repository, branchName)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClient_BranchExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BranchExists'
|
||||
type MockClient_BranchExists_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// BranchExists is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - branchName string
|
||||
func (_e *MockClient_Expecter) BranchExists(ctx interface{}, owner interface{}, repository interface{}, branchName interface{}) *MockClient_BranchExists_Call {
|
||||
return &MockClient_BranchExists_Call{Call: _e.mock.On("BranchExists", ctx, owner, repository, branchName)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_BranchExists_Call) Run(run func(ctx context.Context, owner string, repository string, branchName string)) *MockClient_BranchExists_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_BranchExists_Call) Return(_a0 bool, _a1 error) *MockClient_BranchExists_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_BranchExists_Call) RunAndReturn(run func(context.Context, string, string, string) (bool, error)) *MockClient_BranchExists_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Commits provides a mock function with given fields: ctx, owner, repository, path, branch
|
||||
func (_m *MockClient) Commits(ctx context.Context, owner string, repository string, path string, branch string) ([]Commit, error) {
|
||||
ret := _m.Called(ctx, owner, repository, path, branch)
|
||||
@@ -142,170 +83,6 @@ func (_c *MockClient_Commits_Call) RunAndReturn(run func(context.Context, string
|
||||
return _c
|
||||
}
|
||||
|
||||
// CompareCommits provides a mock function with given fields: ctx, owner, repository, base, head
|
||||
func (_m *MockClient) CompareCommits(ctx context.Context, owner string, repository string, base string, head string) ([]CommitFile, error) {
|
||||
ret := _m.Called(ctx, owner, repository, base, head)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CompareCommits")
|
||||
}
|
||||
|
||||
var r0 []CommitFile
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) ([]CommitFile, error)); ok {
|
||||
return rf(ctx, owner, repository, base, head)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) []CommitFile); ok {
|
||||
r0 = rf(ctx, owner, repository, base, head)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]CommitFile)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string) error); ok {
|
||||
r1 = rf(ctx, owner, repository, base, head)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClient_CompareCommits_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CompareCommits'
|
||||
type MockClient_CompareCommits_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CompareCommits is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - base string
|
||||
// - head string
|
||||
func (_e *MockClient_Expecter) CompareCommits(ctx interface{}, owner interface{}, repository interface{}, base interface{}, head interface{}) *MockClient_CompareCommits_Call {
|
||||
return &MockClient_CompareCommits_Call{Call: _e.mock.On("CompareCommits", ctx, owner, repository, base, head)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_CompareCommits_Call) Run(run func(ctx context.Context, owner string, repository string, base string, head string)) *MockClient_CompareCommits_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_CompareCommits_Call) Return(_a0 []CommitFile, _a1 error) *MockClient_CompareCommits_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_CompareCommits_Call) RunAndReturn(run func(context.Context, string, string, string, string) ([]CommitFile, error)) *MockClient_CompareCommits_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CreateBranch provides a mock function with given fields: ctx, owner, repository, sourceBranch, branchName
|
||||
func (_m *MockClient) CreateBranch(ctx context.Context, owner string, repository string, sourceBranch string, branchName string) error {
|
||||
ret := _m.Called(ctx, owner, repository, sourceBranch, branchName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CreateBranch")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok {
|
||||
r0 = rf(ctx, owner, repository, sourceBranch, branchName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClient_CreateBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateBranch'
|
||||
type MockClient_CreateBranch_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CreateBranch is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - sourceBranch string
|
||||
// - branchName string
|
||||
func (_e *MockClient_Expecter) CreateBranch(ctx interface{}, owner interface{}, repository interface{}, sourceBranch interface{}, branchName interface{}) *MockClient_CreateBranch_Call {
|
||||
return &MockClient_CreateBranch_Call{Call: _e.mock.On("CreateBranch", ctx, owner, repository, sourceBranch, branchName)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_CreateBranch_Call) Run(run func(ctx context.Context, owner string, repository string, sourceBranch string, branchName string)) *MockClient_CreateBranch_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_CreateBranch_Call) Return(_a0 error) *MockClient_CreateBranch_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_CreateBranch_Call) RunAndReturn(run func(context.Context, string, string, string, string) error) *MockClient_CreateBranch_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CreateFile provides a mock function with given fields: ctx, owner, repository, path, branch, message, content
|
||||
func (_m *MockClient) CreateFile(ctx context.Context, owner string, repository string, path string, branch string, message string, content []byte) error {
|
||||
ret := _m.Called(ctx, owner, repository, path, branch, message, content)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CreateFile")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, []byte) error); ok {
|
||||
r0 = rf(ctx, owner, repository, path, branch, message, content)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClient_CreateFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateFile'
|
||||
type MockClient_CreateFile_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CreateFile is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - path string
|
||||
// - branch string
|
||||
// - message string
|
||||
// - content []byte
|
||||
func (_e *MockClient_Expecter) CreateFile(ctx interface{}, owner interface{}, repository interface{}, path interface{}, branch interface{}, message interface{}, content interface{}) *MockClient_CreateFile_Call {
|
||||
return &MockClient_CreateFile_Call{Call: _e.mock.On("CreateFile", ctx, owner, repository, path, branch, message, content)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_CreateFile_Call) Run(run func(ctx context.Context, owner string, repository string, path string, branch string, message string, content []byte)) *MockClient_CreateFile_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(string), args[6].([]byte))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_CreateFile_Call) Return(_a0 error) *MockClient_CreateFile_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_CreateFile_Call) RunAndReturn(run func(context.Context, string, string, string, string, string, []byte) error) *MockClient_CreateFile_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CreatePullRequestComment provides a mock function with given fields: ctx, owner, repository, number, body
|
||||
func (_m *MockClient) CreatePullRequestComment(ctx context.Context, owner string, repository string, number int, body string) error {
|
||||
ret := _m.Called(ctx, owner, repository, number, body)
|
||||
@@ -415,58 +192,6 @@ func (_c *MockClient_CreateWebhook_Call) RunAndReturn(run func(context.Context,
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteFile provides a mock function with given fields: ctx, owner, repository, path, branch, message, hash
|
||||
func (_m *MockClient) DeleteFile(ctx context.Context, owner string, repository string, path string, branch string, message string, hash string) error {
|
||||
ret := _m.Called(ctx, owner, repository, path, branch, message, hash)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteFile")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, string) error); ok {
|
||||
r0 = rf(ctx, owner, repository, path, branch, message, hash)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClient_DeleteFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteFile'
|
||||
type MockClient_DeleteFile_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeleteFile is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - path string
|
||||
// - branch string
|
||||
// - message string
|
||||
// - hash string
|
||||
func (_e *MockClient_Expecter) DeleteFile(ctx interface{}, owner interface{}, repository interface{}, path interface{}, branch interface{}, message interface{}, hash interface{}) *MockClient_DeleteFile_Call {
|
||||
return &MockClient_DeleteFile_Call{Call: _e.mock.On("DeleteFile", ctx, owner, repository, path, branch, message, hash)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_DeleteFile_Call) Run(run func(ctx context.Context, owner string, repository string, path string, branch string, message string, hash string)) *MockClient_DeleteFile_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(string), args[6].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_DeleteFile_Call) Return(_a0 error) *MockClient_DeleteFile_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_DeleteFile_Call) RunAndReturn(run func(context.Context, string, string, string, string, string, string) error) *MockClient_DeleteFile_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteWebhook provides a mock function with given fields: ctx, owner, repository, webhookID
|
||||
func (_m *MockClient) DeleteWebhook(ctx context.Context, owner string, repository string, webhookID int64) error {
|
||||
ret := _m.Called(ctx, owner, repository, webhookID)
|
||||
@@ -565,206 +290,6 @@ func (_c *MockClient_EditWebhook_Call) RunAndReturn(run func(context.Context, st
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetBranch provides a mock function with given fields: ctx, owner, repository, branchName
|
||||
func (_m *MockClient) GetBranch(ctx context.Context, owner string, repository string, branchName string) (Branch, error) {
|
||||
ret := _m.Called(ctx, owner, repository, branchName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetBranch")
|
||||
}
|
||||
|
||||
var r0 Branch
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (Branch, error)); ok {
|
||||
return rf(ctx, owner, repository, branchName)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) Branch); ok {
|
||||
r0 = rf(ctx, owner, repository, branchName)
|
||||
} else {
|
||||
r0 = ret.Get(0).(Branch)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
|
||||
r1 = rf(ctx, owner, repository, branchName)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClient_GetBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBranch'
|
||||
type MockClient_GetBranch_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetBranch is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - branchName string
|
||||
func (_e *MockClient_Expecter) GetBranch(ctx interface{}, owner interface{}, repository interface{}, branchName interface{}) *MockClient_GetBranch_Call {
|
||||
return &MockClient_GetBranch_Call{Call: _e.mock.On("GetBranch", ctx, owner, repository, branchName)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetBranch_Call) Run(run func(ctx context.Context, owner string, repository string, branchName string)) *MockClient_GetBranch_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetBranch_Call) Return(_a0 Branch, _a1 error) *MockClient_GetBranch_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetBranch_Call) RunAndReturn(run func(context.Context, string, string, string) (Branch, error)) *MockClient_GetBranch_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetContents provides a mock function with given fields: ctx, owner, repository, path, ref
|
||||
func (_m *MockClient) GetContents(ctx context.Context, owner string, repository string, path string, ref string) (RepositoryContent, []RepositoryContent, error) {
|
||||
ret := _m.Called(ctx, owner, repository, path, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetContents")
|
||||
}
|
||||
|
||||
var r0 RepositoryContent
|
||||
var r1 []RepositoryContent
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) (RepositoryContent, []RepositoryContent, error)); ok {
|
||||
return rf(ctx, owner, repository, path, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) RepositoryContent); ok {
|
||||
r0 = rf(ctx, owner, repository, path, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(RepositoryContent)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string) []RepositoryContent); ok {
|
||||
r1 = rf(ctx, owner, repository, path, ref)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).([]RepositoryContent)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(context.Context, string, string, string, string) error); ok {
|
||||
r2 = rf(ctx, owner, repository, path, ref)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// MockClient_GetContents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetContents'
|
||||
type MockClient_GetContents_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetContents is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - path string
|
||||
// - ref string
|
||||
func (_e *MockClient_Expecter) GetContents(ctx interface{}, owner interface{}, repository interface{}, path interface{}, ref interface{}) *MockClient_GetContents_Call {
|
||||
return &MockClient_GetContents_Call{Call: _e.mock.On("GetContents", ctx, owner, repository, path, ref)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetContents_Call) Run(run func(ctx context.Context, owner string, repository string, path string, ref string)) *MockClient_GetContents_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetContents_Call) Return(fileContents RepositoryContent, dirContents []RepositoryContent, err error) *MockClient_GetContents_Call {
|
||||
_c.Call.Return(fileContents, dirContents, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetContents_Call) RunAndReturn(run func(context.Context, string, string, string, string) (RepositoryContent, []RepositoryContent, error)) *MockClient_GetContents_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetTree provides a mock function with given fields: ctx, owner, repository, basePath, ref, recursive
|
||||
func (_m *MockClient) GetTree(ctx context.Context, owner string, repository string, basePath string, ref string, recursive bool) ([]RepositoryContent, bool, error) {
|
||||
ret := _m.Called(ctx, owner, repository, basePath, ref, recursive)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetTree")
|
||||
}
|
||||
|
||||
var r0 []RepositoryContent
|
||||
var r1 bool
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool) ([]RepositoryContent, bool, error)); ok {
|
||||
return rf(ctx, owner, repository, basePath, ref, recursive)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool) []RepositoryContent); ok {
|
||||
r0 = rf(ctx, owner, repository, basePath, ref, recursive)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]RepositoryContent)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string, bool) bool); ok {
|
||||
r1 = rf(ctx, owner, repository, basePath, ref, recursive)
|
||||
} else {
|
||||
r1 = ret.Get(1).(bool)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(context.Context, string, string, string, string, bool) error); ok {
|
||||
r2 = rf(ctx, owner, repository, basePath, ref, recursive)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// MockClient_GetTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTree'
|
||||
type MockClient_GetTree_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetTree is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - basePath string
|
||||
// - ref string
|
||||
// - recursive bool
|
||||
func (_e *MockClient_Expecter) GetTree(ctx interface{}, owner interface{}, repository interface{}, basePath interface{}, ref interface{}, recursive interface{}) *MockClient_GetTree_Call {
|
||||
return &MockClient_GetTree_Call{Call: _e.mock.On("GetTree", ctx, owner, repository, basePath, ref, recursive)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetTree_Call) Run(run func(ctx context.Context, owner string, repository string, basePath string, ref string, recursive bool)) *MockClient_GetTree_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetTree_Call) Return(entries []RepositoryContent, truncated bool, err error) *MockClient_GetTree_Call {
|
||||
_c.Call.Return(entries, truncated, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetTree_Call) RunAndReturn(run func(context.Context, string, string, string, string, bool) ([]RepositoryContent, bool, error)) *MockClient_GetTree_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetWebhook provides a mock function with given fields: ctx, owner, repository, webhookID
|
||||
func (_m *MockClient) GetWebhook(ctx context.Context, owner string, repository string, webhookID int64) (WebhookConfig, error) {
|
||||
ret := _m.Called(ctx, owner, repository, webhookID)
|
||||
@@ -824,52 +349,6 @@ func (_c *MockClient_GetWebhook_Call) RunAndReturn(run func(context.Context, str
|
||||
return _c
|
||||
}
|
||||
|
||||
// IsAuthenticated provides a mock function with given fields: ctx
|
||||
func (_m *MockClient) IsAuthenticated(ctx context.Context) error {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IsAuthenticated")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClient_IsAuthenticated_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsAuthenticated'
|
||||
type MockClient_IsAuthenticated_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IsAuthenticated is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockClient_Expecter) IsAuthenticated(ctx interface{}) *MockClient_IsAuthenticated_Call {
|
||||
return &MockClient_IsAuthenticated_Call{Call: _e.mock.On("IsAuthenticated", ctx)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_IsAuthenticated_Call) Run(run func(ctx context.Context)) *MockClient_IsAuthenticated_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_IsAuthenticated_Call) Return(_a0 error) *MockClient_IsAuthenticated_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_IsAuthenticated_Call) RunAndReturn(run func(context.Context) error) *MockClient_IsAuthenticated_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListPullRequestFiles provides a mock function with given fields: ctx, owner, repository, number
|
||||
func (_m *MockClient) ListPullRequestFiles(ctx context.Context, owner string, repository string, number int) ([]CommitFile, error) {
|
||||
ret := _m.Called(ctx, owner, repository, number)
|
||||
@@ -991,117 +470,6 @@ func (_c *MockClient_ListWebhooks_Call) RunAndReturn(run func(context.Context, s
|
||||
return _c
|
||||
}
|
||||
|
||||
// RepoExists provides a mock function with given fields: ctx, owner, repository
|
||||
func (_m *MockClient) RepoExists(ctx context.Context, owner string, repository string) (bool, error) {
|
||||
ret := _m.Called(ctx, owner, repository)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RepoExists")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok {
|
||||
return rf(ctx, owner, repository)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) bool); ok {
|
||||
r0 = rf(ctx, owner, repository)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, owner, repository)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClient_RepoExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepoExists'
|
||||
type MockClient_RepoExists_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RepoExists is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
func (_e *MockClient_Expecter) RepoExists(ctx interface{}, owner interface{}, repository interface{}) *MockClient_RepoExists_Call {
|
||||
return &MockClient_RepoExists_Call{Call: _e.mock.On("RepoExists", ctx, owner, repository)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_RepoExists_Call) Run(run func(ctx context.Context, owner string, repository string)) *MockClient_RepoExists_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_RepoExists_Call) Return(_a0 bool, _a1 error) *MockClient_RepoExists_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_RepoExists_Call) RunAndReturn(run func(context.Context, string, string) (bool, error)) *MockClient_RepoExists_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateFile provides a mock function with given fields: ctx, owner, repository, path, branch, message, hash, content
|
||||
func (_m *MockClient) UpdateFile(ctx context.Context, owner string, repository string, path string, branch string, message string, hash string, content []byte) error {
|
||||
ret := _m.Called(ctx, owner, repository, path, branch, message, hash, content)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateFile")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, string, []byte) error); ok {
|
||||
r0 = rf(ctx, owner, repository, path, branch, message, hash, content)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClient_UpdateFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateFile'
|
||||
type MockClient_UpdateFile_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateFile is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - path string
|
||||
// - branch string
|
||||
// - message string
|
||||
// - hash string
|
||||
// - content []byte
|
||||
func (_e *MockClient_Expecter) UpdateFile(ctx interface{}, owner interface{}, repository interface{}, path interface{}, branch interface{}, message interface{}, hash interface{}, content interface{}) *MockClient_UpdateFile_Call {
|
||||
return &MockClient_UpdateFile_Call{Call: _e.mock.On("UpdateFile", ctx, owner, repository, path, branch, message, hash, content)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_UpdateFile_Call) Run(run func(ctx context.Context, owner string, repository string, path string, branch string, message string, hash string, content []byte)) *MockClient_UpdateFile_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(string), args[6].(string), args[7].([]byte))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_UpdateFile_Call) Return(_a0 error) *MockClient_UpdateFile_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_UpdateFile_Call) RunAndReturn(run func(context.Context, string, string, string, string, string, string, []byte) error) *MockClient_UpdateFile_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockClient(t interface {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package github
|
||||
|
||||
|
||||
@@ -1,312 +0,0 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package github
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
|
||||
// MockRepositoryContent is an autogenerated mock type for the RepositoryContent type
|
||||
type MockRepositoryContent struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockRepositoryContent_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockRepositoryContent) EXPECT() *MockRepositoryContent_Expecter {
|
||||
return &MockRepositoryContent_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetFileContent provides a mock function with no fields
|
||||
func (_m *MockRepositoryContent) GetFileContent() (string, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetFileContent")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (string, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockRepositoryContent_GetFileContent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFileContent'
|
||||
type MockRepositoryContent_GetFileContent_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetFileContent is a helper method to define mock.On call
|
||||
func (_e *MockRepositoryContent_Expecter) GetFileContent() *MockRepositoryContent_GetFileContent_Call {
|
||||
return &MockRepositoryContent_GetFileContent_Call{Call: _e.mock.On("GetFileContent")}
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_GetFileContent_Call) Run(run func()) *MockRepositoryContent_GetFileContent_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_GetFileContent_Call) Return(_a0 string, _a1 error) *MockRepositoryContent_GetFileContent_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_GetFileContent_Call) RunAndReturn(run func() (string, error)) *MockRepositoryContent_GetFileContent_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetPath provides a mock function with no fields
|
||||
func (_m *MockRepositoryContent) GetPath() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPath")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockRepositoryContent_GetPath_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPath'
|
||||
type MockRepositoryContent_GetPath_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetPath is a helper method to define mock.On call
|
||||
func (_e *MockRepositoryContent_Expecter) GetPath() *MockRepositoryContent_GetPath_Call {
|
||||
return &MockRepositoryContent_GetPath_Call{Call: _e.mock.On("GetPath")}
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_GetPath_Call) Run(run func()) *MockRepositoryContent_GetPath_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_GetPath_Call) Return(_a0 string) *MockRepositoryContent_GetPath_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_GetPath_Call) RunAndReturn(run func() string) *MockRepositoryContent_GetPath_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetSHA provides a mock function with no fields
|
||||
func (_m *MockRepositoryContent) GetSHA() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSHA")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockRepositoryContent_GetSHA_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSHA'
|
||||
type MockRepositoryContent_GetSHA_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetSHA is a helper method to define mock.On call
|
||||
func (_e *MockRepositoryContent_Expecter) GetSHA() *MockRepositoryContent_GetSHA_Call {
|
||||
return &MockRepositoryContent_GetSHA_Call{Call: _e.mock.On("GetSHA")}
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_GetSHA_Call) Run(run func()) *MockRepositoryContent_GetSHA_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_GetSHA_Call) Return(_a0 string) *MockRepositoryContent_GetSHA_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_GetSHA_Call) RunAndReturn(run func() string) *MockRepositoryContent_GetSHA_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetSize provides a mock function with no fields
|
||||
func (_m *MockRepositoryContent) GetSize() int64 {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSize")
|
||||
}
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func() int64); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockRepositoryContent_GetSize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSize'
|
||||
type MockRepositoryContent_GetSize_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetSize is a helper method to define mock.On call
|
||||
func (_e *MockRepositoryContent_Expecter) GetSize() *MockRepositoryContent_GetSize_Call {
|
||||
return &MockRepositoryContent_GetSize_Call{Call: _e.mock.On("GetSize")}
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_GetSize_Call) Run(run func()) *MockRepositoryContent_GetSize_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_GetSize_Call) Return(_a0 int64) *MockRepositoryContent_GetSize_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_GetSize_Call) RunAndReturn(run func() int64) *MockRepositoryContent_GetSize_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// IsDirectory provides a mock function with no fields
|
||||
func (_m *MockRepositoryContent) IsDirectory() bool {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IsDirectory")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockRepositoryContent_IsDirectory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsDirectory'
|
||||
type MockRepositoryContent_IsDirectory_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IsDirectory is a helper method to define mock.On call
|
||||
func (_e *MockRepositoryContent_Expecter) IsDirectory() *MockRepositoryContent_IsDirectory_Call {
|
||||
return &MockRepositoryContent_IsDirectory_Call{Call: _e.mock.On("IsDirectory")}
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_IsDirectory_Call) Run(run func()) *MockRepositoryContent_IsDirectory_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_IsDirectory_Call) Return(_a0 bool) *MockRepositoryContent_IsDirectory_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_IsDirectory_Call) RunAndReturn(run func() bool) *MockRepositoryContent_IsDirectory_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// IsSymlink provides a mock function with no fields
|
||||
func (_m *MockRepositoryContent) IsSymlink() bool {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IsSymlink")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockRepositoryContent_IsSymlink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsSymlink'
|
||||
type MockRepositoryContent_IsSymlink_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IsSymlink is a helper method to define mock.On call
|
||||
func (_e *MockRepositoryContent_Expecter) IsSymlink() *MockRepositoryContent_IsSymlink_Call {
|
||||
return &MockRepositoryContent_IsSymlink_Call{Call: _e.mock.On("IsSymlink")}
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_IsSymlink_Call) Run(run func()) *MockRepositoryContent_IsSymlink_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_IsSymlink_Call) Return(_a0 bool) *MockRepositoryContent_IsSymlink_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepositoryContent_IsSymlink_Call) RunAndReturn(run func() bool) *MockRepositoryContent_IsSymlink_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockRepositoryContent creates a new instance of MockRepositoryContent. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockRepositoryContent(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockRepositoryContent {
|
||||
mock := &MockRepositoryContent{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/git"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||
)
|
||||
|
||||
// Make sure all public functions of this struct call the (*githubRepository).logger function, to ensure the GH repo details are included.
|
||||
type githubRepository struct {
|
||||
gitRepo git.GitRepository
|
||||
config *provisioning.Repository
|
||||
gh Client // assumes github.com base URL
|
||||
|
||||
owner string
|
||||
repo string
|
||||
}
|
||||
|
||||
// GithubRepository is an interface that combines all repository capabilities
|
||||
// needed for GitHub repositories.
|
||||
|
||||
//go:generate mockery --name GithubRepository --structname MockGithubRepository --inpackage --filename github_repository_mock.go --with-expecter
|
||||
type GithubRepository interface {
|
||||
repository.Repository
|
||||
repository.Versioned
|
||||
repository.Writer
|
||||
repository.Reader
|
||||
repository.RepositoryWithURLs
|
||||
repository.StageableRepository
|
||||
Owner() string
|
||||
Repo() string
|
||||
Client() Client
|
||||
}
|
||||
|
||||
func NewGitHub(
|
||||
ctx context.Context,
|
||||
config *provisioning.Repository,
|
||||
gitRepo git.GitRepository,
|
||||
factory *Factory,
|
||||
token string,
|
||||
) (GithubRepository, error) {
|
||||
owner, repo, err := ParseOwnerRepoGithub(config.Spec.GitHub.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse owner and repo: %w", err)
|
||||
}
|
||||
|
||||
return &githubRepository{
|
||||
config: config,
|
||||
gitRepo: gitRepo,
|
||||
gh: factory.New(ctx, token), // TODO, baseURL from config
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *githubRepository) Config() *provisioning.Repository {
|
||||
return r.gitRepo.Config()
|
||||
}
|
||||
|
||||
func (r *githubRepository) Owner() string {
|
||||
return r.owner
|
||||
}
|
||||
|
||||
func (r *githubRepository) Repo() string {
|
||||
return r.repo
|
||||
}
|
||||
|
||||
func (r *githubRepository) Client() Client {
|
||||
return r.gh
|
||||
}
|
||||
|
||||
// Validate implements provisioning.Repository.
|
||||
func (r *githubRepository) Validate() (list field.ErrorList) {
|
||||
cfg := r.gitRepo.Config()
|
||||
gh := cfg.Spec.GitHub
|
||||
if gh == nil {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github"), "a github config is required"))
|
||||
return list
|
||||
}
|
||||
if gh.URL == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github", "url"), "a github url is required"))
|
||||
} else {
|
||||
_, _, err := ParseOwnerRepoGithub(gh.URL)
|
||||
if err != nil {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, err.Error()))
|
||||
} else if !strings.HasPrefix(gh.URL, "https://github.com/") {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, "URL must start with https://github.com/"))
|
||||
}
|
||||
}
|
||||
|
||||
if len(list) > 0 {
|
||||
return list
|
||||
}
|
||||
|
||||
return r.gitRepo.Validate()
|
||||
}
|
||||
|
||||
func ParseOwnerRepoGithub(giturl string) (owner string, repo string, err error) {
|
||||
parsed, e := url.Parse(strings.TrimSuffix(giturl, ".git"))
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
parts := strings.Split(parsed.Path, "/")
|
||||
if len(parts) < 3 {
|
||||
err = fmt.Errorf("unable to parse repo+owner from url")
|
||||
return
|
||||
}
|
||||
return parts[1], parts[2], nil
|
||||
}
|
||||
|
||||
// Test implements provisioning.Repository.
|
||||
func (r *githubRepository) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
||||
url := r.config.Spec.GitHub.URL
|
||||
_, _, err := ParseOwnerRepoGithub(url)
|
||||
if err != nil {
|
||||
return repository.FromFieldError(field.Invalid(
|
||||
field.NewPath("spec", "github", "url"), url, err.Error())), nil
|
||||
}
|
||||
|
||||
return r.gitRepo.Test(ctx)
|
||||
}
|
||||
|
||||
// ReadResource implements provisioning.Repository.
|
||||
func (r *githubRepository) Read(ctx context.Context, filePath, ref string) (*repository.FileInfo, error) {
|
||||
return r.gitRepo.Read(ctx, filePath, ref)
|
||||
}
|
||||
|
||||
func (r *githubRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
||||
return r.gitRepo.ReadTree(ctx, ref)
|
||||
}
|
||||
|
||||
func (r *githubRepository) Create(ctx context.Context, path, ref string, data []byte, comment string) error {
|
||||
return r.gitRepo.Create(ctx, path, ref, data, comment)
|
||||
}
|
||||
|
||||
func (r *githubRepository) Update(ctx context.Context, path, ref string, data []byte, comment string) error {
|
||||
return r.gitRepo.Update(ctx, path, ref, data, comment)
|
||||
}
|
||||
|
||||
func (r *githubRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||
return r.gitRepo.Write(ctx, path, ref, data, message)
|
||||
}
|
||||
|
||||
func (r *githubRepository) Delete(ctx context.Context, path, ref, comment string) error {
|
||||
return r.gitRepo.Delete(ctx, path, ref, comment)
|
||||
}
|
||||
|
||||
func (r *githubRepository) History(ctx context.Context, path, ref string) ([]provisioning.HistoryItem, error) {
|
||||
if ref == "" {
|
||||
ref = r.config.Spec.GitHub.Branch
|
||||
}
|
||||
|
||||
finalPath := safepath.Join(r.config.Spec.GitHub.Path, path)
|
||||
commits, err := r.gh.Commits(ctx, r.owner, r.repo, finalPath, ref)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrResourceNotFound) {
|
||||
return nil, repository.ErrFileNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("get commits: %w", err)
|
||||
}
|
||||
|
||||
ret := make([]provisioning.HistoryItem, 0, len(commits))
|
||||
for _, commit := range commits {
|
||||
authors := make([]provisioning.Author, 0)
|
||||
if commit.Author != nil {
|
||||
authors = append(authors, provisioning.Author{
|
||||
Name: commit.Author.Name,
|
||||
Username: commit.Author.Username,
|
||||
AvatarURL: commit.Author.AvatarURL,
|
||||
})
|
||||
}
|
||||
|
||||
if commit.Committer != nil && commit.Author != nil && commit.Author.Name != commit.Committer.Name {
|
||||
authors = append(authors, provisioning.Author{
|
||||
Name: commit.Committer.Name,
|
||||
Username: commit.Committer.Username,
|
||||
AvatarURL: commit.Committer.AvatarURL,
|
||||
})
|
||||
}
|
||||
|
||||
ret = append(ret, provisioning.HistoryItem{
|
||||
Ref: commit.Ref,
|
||||
Message: commit.Message,
|
||||
Authors: authors,
|
||||
CreatedAt: commit.CreatedAt.UnixMilli(),
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *githubRepository) LatestRef(ctx context.Context) (string, error) {
|
||||
return r.gitRepo.LatestRef(ctx)
|
||||
}
|
||||
|
||||
func (r *githubRepository) CompareFiles(ctx context.Context, base, ref string) ([]repository.VersionedFileChange, error) {
|
||||
return r.gitRepo.CompareFiles(ctx, base, ref)
|
||||
}
|
||||
|
||||
// ResourceURLs implements RepositoryWithURLs.
|
||||
func (r *githubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*provisioning.ResourceURLs, error) {
|
||||
cfg := r.config.Spec.GitHub
|
||||
if file.Path == "" || cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ref := file.Ref
|
||||
if ref == "" {
|
||||
ref = cfg.Branch
|
||||
}
|
||||
|
||||
urls := &provisioning.ResourceURLs{
|
||||
RepositoryURL: cfg.URL,
|
||||
SourceURL: fmt.Sprintf("%s/blob/%s/%s", cfg.URL, ref, file.Path),
|
||||
}
|
||||
|
||||
if ref != cfg.Branch {
|
||||
urls.CompareURL = fmt.Sprintf("%s/compare/%s...%s", cfg.URL, cfg.Branch, ref)
|
||||
|
||||
// Create a new pull request
|
||||
urls.NewPullRequestURL = fmt.Sprintf("%s?quick_pull=1&labels=grafana", urls.CompareURL)
|
||||
}
|
||||
|
||||
return urls, nil
|
||||
}
|
||||
|
||||
func (r *githubRepository) Stage(ctx context.Context, opts repository.StageOptions) (repository.StagedRepository, error) {
|
||||
return r.gitRepo.Stage(ctx, opts)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,47 +0,0 @@
|
||||
package gogit
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
func Progress(lines func(line string), final string) io.WriteCloser {
|
||||
reader, writer := io.Pipe()
|
||||
scanner := bufio.NewScanner(reader)
|
||||
scanner.Split(scanLines)
|
||||
go func() {
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if line != "" {
|
||||
lines(line)
|
||||
}
|
||||
}
|
||||
lines(final)
|
||||
}()
|
||||
return writer
|
||||
}
|
||||
|
||||
// Copied from bufio.ScanLines and modifed to accept standalone \r as input
|
||||
func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if atEOF && len(data) == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
if i := bytes.IndexByte(data, '\r'); i >= 0 {
|
||||
// We have a full newline-terminated line.
|
||||
return i + 1, data[0:i], nil
|
||||
}
|
||||
|
||||
// Support standalone newlines also
|
||||
if i := bytes.IndexByte(data, '\n'); i >= 0 {
|
||||
// We have a full newline-terminated line.
|
||||
return i + 1, data[0:i], nil
|
||||
}
|
||||
|
||||
// If we're at EOF, we have a final, non-terminated line. Return it.
|
||||
if atEOF {
|
||||
return len(data), data, nil
|
||||
}
|
||||
// Request more data.
|
||||
return 0, nil, nil
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package gogit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProgressParsing(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expect []string
|
||||
}{
|
||||
{
|
||||
name: "no breaks",
|
||||
input: "some text",
|
||||
expect: []string{"some text"},
|
||||
},
|
||||
{
|
||||
name: "with cr",
|
||||
input: "hello\rworld",
|
||||
expect: []string{"hello", "world"},
|
||||
},
|
||||
{
|
||||
name: "with nl",
|
||||
input: "hello\nworld",
|
||||
expect: []string{"hello", "world"},
|
||||
},
|
||||
{
|
||||
name: "with cr+nl",
|
||||
input: "hello\r\nworld",
|
||||
expect: []string{"hello", "world"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
lastLine := "***LAST*LINE***"
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
lines := []string{}
|
||||
writer := Progress(func(line string) {
|
||||
lines = append(lines, line)
|
||||
}, lastLine)
|
||||
_, _ = writer.Write([]byte(tt.input))
|
||||
err := writer.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
assert.NotEmpty(c, lines)
|
||||
assert.Equal(c, lastLine, lines[len(lines)-1])
|
||||
|
||||
// Compare the results
|
||||
require.Equal(c, tt.expect, lines[0:len(lines)-1])
|
||||
}, time.Millisecond*100, time.Microsecond*50)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package gogit
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
git "github.com/go-git/go-git/v5"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockRepository is an autogenerated mock type for the Repository type
|
||||
type MockRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockRepository_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockRepository) EXPECT() *MockRepository_Expecter {
|
||||
return &MockRepository_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// PushContext provides a mock function with given fields: ctx, o
|
||||
func (_m *MockRepository) PushContext(ctx context.Context, o *git.PushOptions) error {
|
||||
ret := _m.Called(ctx, o)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PushContext")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *git.PushOptions) error); ok {
|
||||
r0 = rf(ctx, o)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockRepository_PushContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PushContext'
|
||||
type MockRepository_PushContext_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PushContext is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - o *git.PushOptions
|
||||
func (_e *MockRepository_Expecter) PushContext(ctx interface{}, o interface{}) *MockRepository_PushContext_Call {
|
||||
return &MockRepository_PushContext_Call{Call: _e.mock.On("PushContext", ctx, o)}
|
||||
}
|
||||
|
||||
func (_c *MockRepository_PushContext_Call) Run(run func(ctx context.Context, o *git.PushOptions)) *MockRepository_PushContext_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(*git.PushOptions))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepository_PushContext_Call) Return(_a0 error) *MockRepository_PushContext_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockRepository_PushContext_Call) RunAndReturn(run func(context.Context, *git.PushOptions) error) *MockRepository_PushContext_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockRepository creates a new instance of MockRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockRepository(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockRepository {
|
||||
mock := &MockRepository{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package gogit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util/httpclient"
|
||||
)
|
||||
|
||||
var errBytesLimitExceeded = fmt.Errorf("bytes limit exceeded")
|
||||
|
||||
// ByteLimitedTransport wraps http.RoundTripper to enforce a max byte limit
|
||||
type ByteLimitedTransport struct {
|
||||
Transport http.RoundTripper
|
||||
Limit int64
|
||||
Bytes int64
|
||||
}
|
||||
|
||||
// NewByteLimitedTransport creates a new ByteLimitedTransport with the specified transport and byte limit.
|
||||
// If transport is nil, a new http.Transport modeled after http.DefaultTransport will be used.
|
||||
func NewByteLimitedTransport(transport http.RoundTripper, limit int64) *ByteLimitedTransport {
|
||||
if transport == nil {
|
||||
transport = httpclient.NewHTTPTransport()
|
||||
}
|
||||
return &ByteLimitedTransport{
|
||||
Transport: transport,
|
||||
Limit: limit,
|
||||
Bytes: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// RoundTrip tracks downloaded bytes and aborts if limit is exceeded
|
||||
func (b *ByteLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
resp, err := b.Transport.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wrap response body to track bytes read
|
||||
resp.Body = &byteLimitedReader{
|
||||
reader: resp.Body,
|
||||
limit: b.Limit,
|
||||
bytes: &b.Bytes,
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// byteLimitedReader tracks and enforces a download limit
|
||||
type byteLimitedReader struct {
|
||||
reader io.ReadCloser
|
||||
limit int64
|
||||
bytes *int64
|
||||
}
|
||||
|
||||
func (r *byteLimitedReader) Read(p []byte) (int, error) {
|
||||
n, err := r.reader.Read(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if atomic.AddInt64(r.bytes, int64(n)) > r.limit {
|
||||
return 0, errBytesLimitExceeded
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *byteLimitedReader) Close() error {
|
||||
return r.reader.Close()
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package gogit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type mockTransport struct {
|
||||
response *http.Response
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *mockTransport) RoundTrip(*http.Request) (*http.Response, error) {
|
||||
return m.response, m.err
|
||||
}
|
||||
|
||||
func TestNewByteLimitedTransport(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
transport http.RoundTripper
|
||||
limit int64
|
||||
}{
|
||||
{
|
||||
name: "with custom transport",
|
||||
transport: &mockTransport{},
|
||||
limit: 1000,
|
||||
},
|
||||
{
|
||||
name: "with nil transport",
|
||||
transport: nil,
|
||||
limit: 1000,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
blt := NewByteLimitedTransport(tt.transport, tt.limit)
|
||||
assert.NotNil(t, blt)
|
||||
assert.Equal(t, tt.limit, blt.Limit)
|
||||
assert.Equal(t, int64(0), blt.Bytes)
|
||||
|
||||
if tt.transport == nil {
|
||||
assert.NotNil(t, blt.Transport)
|
||||
assert.NotEqual(t, http.DefaultTransport, blt.Transport)
|
||||
} else {
|
||||
assert.Equal(t, tt.transport, blt.Transport)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteLimitedTransport_RoundTrip(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
responseBody string
|
||||
limit int64
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "under limit",
|
||||
responseBody: "small response",
|
||||
limit: 100,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "exceeds limit",
|
||||
responseBody: "this response will exceed the byte limit",
|
||||
limit: 10,
|
||||
expectedError: errBytesLimitExceeded,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockResp := &http.Response{
|
||||
Body: io.NopCloser(bytes.NewBufferString(tt.responseBody)),
|
||||
}
|
||||
mockTransport := &mockTransport{response: mockResp}
|
||||
|
||||
blt := NewByteLimitedTransport(mockTransport, tt.limit)
|
||||
resp, err := blt.RoundTrip(&http.Request{})
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
closeErr := resp.Body.Close()
|
||||
assert.NoError(t, closeErr, "failed to close response body")
|
||||
}()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if tt.expectedError != nil {
|
||||
assert.True(t, errors.Is(err, tt.expectedError), "expected error %v, got %v", tt.expectedError, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.responseBody, string(data))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteLimitedReader_Close(t *testing.T) {
|
||||
mockBody := io.NopCloser(bytes.NewBufferString("test"))
|
||||
var byteCount int64
|
||||
reader := &byteLimitedReader{
|
||||
reader: mockBody,
|
||||
limit: 100,
|
||||
bytes: &byteCount,
|
||||
}
|
||||
|
||||
err := reader.Close()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestByteLimitedReader_AtomicCounting(t *testing.T) {
|
||||
var byteCount int64
|
||||
reader := &byteLimitedReader{
|
||||
reader: io.NopCloser(bytes.NewBufferString("test data")),
|
||||
limit: 5,
|
||||
bytes: &byteCount,
|
||||
}
|
||||
|
||||
// First read should succeed
|
||||
buf := make([]byte, 4)
|
||||
n, err := reader.Read(buf)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 4, n)
|
||||
|
||||
// Second read should fail due to limit
|
||||
n, err = reader.Read(buf)
|
||||
assert.True(t, errors.Is(err, errBytesLimitExceeded), "expected error %v, got %v", errBytesLimitExceeded, err)
|
||||
assert.Equal(t, 0, n)
|
||||
|
||||
// Verify atomic counter
|
||||
assert.Greater(t, atomic.LoadInt64(&byteCount), int64(5))
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package gogit
|
||||
|
||||
import (
|
||||
billy "github.com/go-git/go-billy/v5"
|
||||
git "github.com/go-git/go-git/v5"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
plumbing "github.com/go-git/go-git/v5/plumbing"
|
||||
)
|
||||
|
||||
// MockWorktree is an autogenerated mock type for the Worktree type
|
||||
type MockWorktree struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockWorktree_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockWorktree) EXPECT() *MockWorktree_Expecter {
|
||||
return &MockWorktree_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Add provides a mock function with given fields: path
|
||||
func (_m *MockWorktree) Add(path string) (plumbing.Hash, error) {
|
||||
ret := _m.Called(path)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Add")
|
||||
}
|
||||
|
||||
var r0 plumbing.Hash
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (plumbing.Hash, error)); ok {
|
||||
return rf(path)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) plumbing.Hash); ok {
|
||||
r0 = rf(path)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(plumbing.Hash)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(path)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockWorktree_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add'
|
||||
type MockWorktree_Add_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Add is a helper method to define mock.On call
|
||||
// - path string
|
||||
func (_e *MockWorktree_Expecter) Add(path interface{}) *MockWorktree_Add_Call {
|
||||
return &MockWorktree_Add_Call{Call: _e.mock.On("Add", path)}
|
||||
}
|
||||
|
||||
func (_c *MockWorktree_Add_Call) Run(run func(path string)) *MockWorktree_Add_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWorktree_Add_Call) Return(_a0 plumbing.Hash, _a1 error) *MockWorktree_Add_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWorktree_Add_Call) RunAndReturn(run func(string) (plumbing.Hash, error)) *MockWorktree_Add_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Commit provides a mock function with given fields: message, opts
|
||||
func (_m *MockWorktree) Commit(message string, opts *git.CommitOptions) (plumbing.Hash, error) {
|
||||
ret := _m.Called(message, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Commit")
|
||||
}
|
||||
|
||||
var r0 plumbing.Hash
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, *git.CommitOptions) (plumbing.Hash, error)); ok {
|
||||
return rf(message, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, *git.CommitOptions) plumbing.Hash); ok {
|
||||
r0 = rf(message, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(plumbing.Hash)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, *git.CommitOptions) error); ok {
|
||||
r1 = rf(message, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockWorktree_Commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Commit'
|
||||
type MockWorktree_Commit_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Commit is a helper method to define mock.On call
|
||||
// - message string
|
||||
// - opts *git.CommitOptions
|
||||
func (_e *MockWorktree_Expecter) Commit(message interface{}, opts interface{}) *MockWorktree_Commit_Call {
|
||||
return &MockWorktree_Commit_Call{Call: _e.mock.On("Commit", message, opts)}
|
||||
}
|
||||
|
||||
func (_c *MockWorktree_Commit_Call) Run(run func(message string, opts *git.CommitOptions)) *MockWorktree_Commit_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(*git.CommitOptions))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWorktree_Commit_Call) Return(_a0 plumbing.Hash, _a1 error) *MockWorktree_Commit_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWorktree_Commit_Call) RunAndReturn(run func(string, *git.CommitOptions) (plumbing.Hash, error)) *MockWorktree_Commit_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Filesystem provides a mock function with no fields
|
||||
func (_m *MockWorktree) Filesystem() billy.Filesystem {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Filesystem")
|
||||
}
|
||||
|
||||
var r0 billy.Filesystem
|
||||
if rf, ok := ret.Get(0).(func() billy.Filesystem); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(billy.Filesystem)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockWorktree_Filesystem_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Filesystem'
|
||||
type MockWorktree_Filesystem_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Filesystem is a helper method to define mock.On call
|
||||
func (_e *MockWorktree_Expecter) Filesystem() *MockWorktree_Filesystem_Call {
|
||||
return &MockWorktree_Filesystem_Call{Call: _e.mock.On("Filesystem")}
|
||||
}
|
||||
|
||||
func (_c *MockWorktree_Filesystem_Call) Run(run func()) *MockWorktree_Filesystem_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWorktree_Filesystem_Call) Return(_a0 billy.Filesystem) *MockWorktree_Filesystem_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWorktree_Filesystem_Call) RunAndReturn(run func() billy.Filesystem) *MockWorktree_Filesystem_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Remove provides a mock function with given fields: path
|
||||
func (_m *MockWorktree) Remove(path string) (plumbing.Hash, error) {
|
||||
ret := _m.Called(path)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Remove")
|
||||
}
|
||||
|
||||
var r0 plumbing.Hash
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (plumbing.Hash, error)); ok {
|
||||
return rf(path)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) plumbing.Hash); ok {
|
||||
r0 = rf(path)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(plumbing.Hash)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(path)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockWorktree_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove'
|
||||
type MockWorktree_Remove_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Remove is a helper method to define mock.On call
|
||||
// - path string
|
||||
func (_e *MockWorktree_Expecter) Remove(path interface{}) *MockWorktree_Remove_Call {
|
||||
return &MockWorktree_Remove_Call{Call: _e.mock.On("Remove", path)}
|
||||
}
|
||||
|
||||
func (_c *MockWorktree_Remove_Call) Run(run func(path string)) *MockWorktree_Remove_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWorktree_Remove_Call) Return(_a0 plumbing.Hash, _a1 error) *MockWorktree_Remove_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWorktree_Remove_Call) RunAndReturn(run func(string) (plumbing.Hash, error)) *MockWorktree_Remove_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockWorktree creates a new instance of MockWorktree. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockWorktree(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockWorktree {
|
||||
mock := &MockWorktree{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -1,468 +0,0 @@
|
||||
package gogit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-billy/v5"
|
||||
"github.com/go-git/go-billy/v5/util"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/client"
|
||||
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
||||
"github.com/grafana/grafana/pkg/util/httpclient"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxOperationBytes is the maximum size of a git operation in bytes (1 GB)
|
||||
maxOperationBytes = int64(1 << 30)
|
||||
maxOperationTimeout = 10 * time.Minute
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Create a size-limited writer that will cancel the context if size is exceeded
|
||||
limitedTransport := NewByteLimitedTransport(httpclient.NewHTTPTransport(), maxOperationBytes)
|
||||
httpClient := githttp.NewClient(&http.Client{
|
||||
Transport: limitedTransport,
|
||||
})
|
||||
client.InstallProtocol("https", httpClient)
|
||||
client.InstallProtocol("http", httpClient)
|
||||
}
|
||||
|
||||
//go:generate mockery --name=Worktree --output=mocks --inpackage --filename=worktree_mock.go --with-expecter
|
||||
type Worktree interface {
|
||||
Commit(message string, opts *git.CommitOptions) (plumbing.Hash, error)
|
||||
Remove(path string) (plumbing.Hash, error)
|
||||
Add(path string) (plumbing.Hash, error)
|
||||
Filesystem() billy.Filesystem
|
||||
}
|
||||
|
||||
type worktree struct {
|
||||
*git.Worktree
|
||||
}
|
||||
|
||||
//go:generate mockery --name=Repository --output=mocks --inpackage --filename=repository_mock.go --with-expecter
|
||||
type Repository interface {
|
||||
PushContext(ctx context.Context, o *git.PushOptions) error
|
||||
}
|
||||
|
||||
func (w *worktree) Filesystem() billy.Filesystem {
|
||||
return w.Worktree.Filesystem
|
||||
}
|
||||
|
||||
var _ repository.Repository = (*GoGitRepo)(nil)
|
||||
|
||||
type GoGitRepo struct {
|
||||
config *provisioning.Repository
|
||||
decryptedPassword string
|
||||
opts repository.CloneOptions
|
||||
|
||||
repo Repository
|
||||
tree Worktree
|
||||
dir string // file path to worktree root (necessary? should use billy)
|
||||
}
|
||||
|
||||
// This will create a new clone every time
|
||||
// As structured, it is valid for one context and should not be shared across multiple requests
|
||||
func Clone(
|
||||
ctx context.Context,
|
||||
root string,
|
||||
config *provisioning.Repository,
|
||||
opts repository.CloneOptions,
|
||||
secrets secrets.Service,
|
||||
) (repository.ClonedRepository, error) {
|
||||
if root == "" {
|
||||
return nil, fmt.Errorf("missing root config")
|
||||
}
|
||||
|
||||
if config.Namespace == "" {
|
||||
return nil, fmt.Errorf("config is missing namespace")
|
||||
}
|
||||
|
||||
if config.Name == "" {
|
||||
return nil, fmt.Errorf("config is missing name")
|
||||
}
|
||||
|
||||
if opts.BeforeFn != nil {
|
||||
if err := opts.BeforeFn(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// add a timeout to the operation
|
||||
timeout := maxOperationTimeout
|
||||
if opts.Timeout > 0 {
|
||||
timeout = opts.Timeout
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
decrypted, err := secrets.Decrypt(ctx, config.Spec.GitHub.EncryptedToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decrypting token: %w", err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(root, 0700); err != nil {
|
||||
return nil, fmt.Errorf("create root dir: %w", err)
|
||||
}
|
||||
|
||||
dir, err := os.MkdirTemp(root, fmt.Sprintf("clone-%s-%s-", config.Namespace, config.Name))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create temp clone dir: %w", err)
|
||||
}
|
||||
|
||||
progress := opts.Progress
|
||||
if progress == nil {
|
||||
progress = io.Discard
|
||||
}
|
||||
|
||||
repo, tree, err := clone(ctx, config, opts, decrypted, dir, progress)
|
||||
if err != nil {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
return nil, fmt.Errorf("remove temp clone dir after clone failed: %w", err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("clone: %w", err)
|
||||
}
|
||||
|
||||
return &GoGitRepo{
|
||||
config: config,
|
||||
tree: &worktree{Worktree: tree},
|
||||
opts: opts,
|
||||
decryptedPassword: string(decrypted),
|
||||
repo: repo,
|
||||
dir: dir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func clone(ctx context.Context, config *provisioning.Repository, opts repository.CloneOptions, decrypted []byte, dir string, progress io.Writer) (*git.Repository, *git.Worktree, error) {
|
||||
gitcfg := config.Spec.GitHub
|
||||
url := gitcfg.URL
|
||||
if !strings.HasPrefix(url, "file://") {
|
||||
url = fmt.Sprintf("%s.git", url)
|
||||
}
|
||||
|
||||
branch := plumbing.NewBranchReferenceName(gitcfg.Branch)
|
||||
cloneOpts := &git.CloneOptions{
|
||||
ReferenceName: branch,
|
||||
Auth: &githttp.BasicAuth{
|
||||
Username: "grafana", // this can be anything except an empty string for PAT
|
||||
Password: string(decrypted), // TODO... will need to get from a service!
|
||||
},
|
||||
URL: url,
|
||||
Progress: progress,
|
||||
}
|
||||
|
||||
repo, err := git.PlainCloneContext(ctx, dir, false, cloneOpts)
|
||||
if errors.Is(err, plumbing.ErrReferenceNotFound) && opts.CreateIfNotExists {
|
||||
cloneOpts.ReferenceName = "" // empty
|
||||
repo, err = git.PlainCloneContext(ctx, dir, false, cloneOpts)
|
||||
if err == nil {
|
||||
worktree, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = worktree.Checkout(&git.CheckoutOptions{
|
||||
Branch: branch,
|
||||
Force: true,
|
||||
Create: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create new branch: %w", err)
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, nil, fmt.Errorf("clone error: %w", err)
|
||||
}
|
||||
|
||||
rcfg, err := repo.Config()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading repository config %w", err)
|
||||
}
|
||||
|
||||
origin := rcfg.Remotes["origin"]
|
||||
if origin == nil {
|
||||
return nil, nil, fmt.Errorf("missing origin remote %w", err)
|
||||
}
|
||||
|
||||
if url != origin.URLs[0] {
|
||||
return nil, nil, fmt.Errorf("unexpected remote (expected: %s, found: %s)", url, origin.URLs[0])
|
||||
}
|
||||
|
||||
worktree, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("get worktree: %w", err)
|
||||
}
|
||||
|
||||
return repo, worktree, nil
|
||||
}
|
||||
|
||||
// After making changes to the worktree, push changes
|
||||
func (g *GoGitRepo) Push(ctx context.Context, opts repository.PushOptions) error {
|
||||
timeout := maxOperationTimeout
|
||||
if opts.Timeout > 0 {
|
||||
timeout = opts.Timeout
|
||||
}
|
||||
|
||||
progress := opts.Progress
|
||||
if progress == nil {
|
||||
progress = io.Discard
|
||||
}
|
||||
|
||||
if opts.BeforeFn != nil {
|
||||
if err := opts.BeforeFn(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
if !g.opts.PushOnWrites {
|
||||
_, err := g.tree.Commit("exported from grafana", &git.CommitOptions{
|
||||
All: true, // Add everything that changed
|
||||
})
|
||||
if err != nil {
|
||||
// empty commit is fine -- no change
|
||||
if !errors.Is(err, git.ErrEmptyCommit) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := g.repo.PushContext(ctx, &git.PushOptions{
|
||||
Progress: progress,
|
||||
Force: true, // avoid fast-forward-errors
|
||||
Auth: &githttp.BasicAuth{ // reuse logic from clone?
|
||||
Username: "grafana",
|
||||
Password: g.decryptedPassword,
|
||||
},
|
||||
})
|
||||
if errors.Is(err, git.NoErrAlreadyUpToDate) {
|
||||
return nil // same as the target
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (g *GoGitRepo) Remove(ctx context.Context) error {
|
||||
return os.RemoveAll(g.dir)
|
||||
}
|
||||
|
||||
// Config implements repository.Repository.
|
||||
func (g *GoGitRepo) Config() *provisioning.Repository {
|
||||
return g.config
|
||||
}
|
||||
|
||||
// ReadTree implements repository.Repository.
|
||||
func (g *GoGitRepo) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
||||
var treePath string
|
||||
if g.config.Spec.GitHub.Path != "" {
|
||||
treePath = g.config.Spec.GitHub.Path
|
||||
}
|
||||
treePath = safepath.Clean(treePath)
|
||||
|
||||
entries := make([]repository.FileTreeEntry, 0, 100)
|
||||
err := util.Walk(g.tree.Filesystem(), treePath, func(path string, info fs.FileInfo, err error) error {
|
||||
// We already have an error, just pass it onwards.
|
||||
if err != nil ||
|
||||
// This is the root of the repository (or should pretend to be)
|
||||
safepath.Clean(path) == "" || path == treePath ||
|
||||
// This is the Git data
|
||||
(treePath == "" && (strings.HasPrefix(path, ".git/") || path == ".git")) {
|
||||
return err
|
||||
}
|
||||
if treePath != "" {
|
||||
path = strings.TrimPrefix(path, treePath)
|
||||
}
|
||||
entry := repository.FileTreeEntry{
|
||||
Path: strings.TrimLeft(path, "/"),
|
||||
Size: info.Size(),
|
||||
}
|
||||
if !info.IsDir() {
|
||||
entry.Blob = true
|
||||
// For a real instance, this will likely be based on:
|
||||
// https://github.com/go-git/go-git/blob/main/_examples/ls/main.go#L25
|
||||
entry.Hash = fmt.Sprintf("TODO/%d", info.Size()) // but not used for
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
return err
|
||||
})
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// We intentionally ignore this case, as it is expected
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("walk tree for ref '%s': %w", ref, err)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (g *GoGitRepo) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
||||
return &provisioning.TestResults{
|
||||
Success: g.tree != nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Update implements repository.Repository.
|
||||
func (g *GoGitRepo) Update(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||
return g.Write(ctx, path, ref, data, message)
|
||||
}
|
||||
|
||||
// Create implements repository.Repository.
|
||||
func (g *GoGitRepo) Create(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||
// FIXME: this means we would override files
|
||||
return g.Write(ctx, path, ref, data, message)
|
||||
}
|
||||
|
||||
// Write implements repository.Repository.
|
||||
func (g *GoGitRepo) Write(ctx context.Context, fpath string, ref string, data []byte, message string) error {
|
||||
if err := verifyPathWithoutRef(fpath, ref); err != nil {
|
||||
return err
|
||||
}
|
||||
fpath = safepath.Join(g.config.Spec.GitHub.Path, fpath)
|
||||
|
||||
// FIXME: this means that won't export empty folders
|
||||
// should we create them with a .keep file?
|
||||
// For folders, just create the folder and ignore the commit
|
||||
if safepath.IsDir(fpath) {
|
||||
return g.tree.Filesystem().MkdirAll(fpath, 0750)
|
||||
}
|
||||
|
||||
dir := safepath.Dir(fpath)
|
||||
if dir != "" {
|
||||
err := g.tree.Filesystem().MkdirAll(dir, 0750)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
file, err := g.tree.Filesystem().Create(fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = file.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = g.tree.Add(fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return g.maybeCommit(ctx, message)
|
||||
}
|
||||
|
||||
func (g *GoGitRepo) maybeCommit(ctx context.Context, message string) error {
|
||||
// Skip commit for each file
|
||||
if !g.opts.PushOnWrites {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := &git.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: "grafana",
|
||||
},
|
||||
}
|
||||
sig := repository.GetAuthorSignature(ctx)
|
||||
if sig != nil && sig.Name != "" {
|
||||
opts.Author.Name = sig.Name
|
||||
opts.Author.Email = sig.Email
|
||||
opts.Author.When = sig.When
|
||||
}
|
||||
if opts.Author.When.IsZero() {
|
||||
opts.Author.When = time.Now()
|
||||
}
|
||||
|
||||
_, err := g.tree.Commit(message, opts)
|
||||
if errors.Is(err, git.ErrEmptyCommit) {
|
||||
return nil // empty commit is fine -- no change
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete implements repository.Repository.
|
||||
func (g *GoGitRepo) Delete(ctx context.Context, fpath string, ref string, message string) error {
|
||||
if err := verifyPathWithoutRef(fpath, ref); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fpath = safepath.Join(g.config.Spec.GitHub.Path, fpath)
|
||||
if _, err := g.tree.Remove(fpath); err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return repository.ErrFileNotFound
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
return g.maybeCommit(ctx, message)
|
||||
}
|
||||
|
||||
// Read implements repository.Repository.
|
||||
func (g *GoGitRepo) Read(ctx context.Context, path string, ref string) (*repository.FileInfo, error) {
|
||||
if err := verifyPathWithoutRef(path, ref); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readPath := safepath.Join(g.config.Spec.GitHub.Path, path)
|
||||
stat, err := g.tree.Filesystem().Lstat(readPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, repository.ErrFileNotFound
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("stat path '%s': %w", readPath, err)
|
||||
}
|
||||
info := &repository.FileInfo{
|
||||
Path: path,
|
||||
Modified: &metav1.Time{
|
||||
Time: stat.ModTime(),
|
||||
},
|
||||
}
|
||||
if !stat.IsDir() {
|
||||
f, err := g.tree.Filesystem().Open(readPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open file '%s': %w", readPath, err)
|
||||
}
|
||||
info.Data, err = io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read file '%s': %w", readPath, err)
|
||||
}
|
||||
}
|
||||
return info, err
|
||||
}
|
||||
|
||||
func verifyPathWithoutRef(path string, ref string) error {
|
||||
if path == "" {
|
||||
return fmt.Errorf("expected path")
|
||||
}
|
||||
if ref != "" {
|
||||
return fmt.Errorf("ref unsupported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// History implements repository.Repository.
|
||||
func (g *GoGitRepo) History(ctx context.Context, path string, ref string) ([]provisioning.HistoryItem, error) {
|
||||
return nil, &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Message: "history is not yet implemented",
|
||||
Code: http.StatusNotImplemented,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validate implements repository.Repository.
|
||||
func (g *GoGitRepo) Validate() field.ErrorList {
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+18
-17
@@ -1,4 +1,4 @@
|
||||
package repository
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||
)
|
||||
|
||||
@@ -75,9 +76,9 @@ func (r *LocalFolderResolver) LocalPath(p string) (string, error) {
|
||||
}
|
||||
|
||||
var (
|
||||
_ Repository = (*localRepository)(nil)
|
||||
_ Writer = (*localRepository)(nil)
|
||||
_ Reader = (*localRepository)(nil)
|
||||
_ repository.Repository = (*localRepository)(nil)
|
||||
_ repository.Writer = (*localRepository)(nil)
|
||||
_ repository.Reader = (*localRepository)(nil)
|
||||
)
|
||||
|
||||
type localRepository struct {
|
||||
@@ -147,17 +148,17 @@ func (r *localRepository) Validate() field.ErrorList {
|
||||
func (r *localRepository) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
||||
path := field.NewPath("spec", "local", "path")
|
||||
if r.config.Spec.Local.Path == "" {
|
||||
return fromFieldError(field.Required(path, "no path is configured")), nil
|
||||
return repository.FromFieldError(field.Required(path, "no path is configured")), nil
|
||||
}
|
||||
|
||||
_, err := r.resolver.LocalPath(r.config.Spec.Local.Path)
|
||||
if err != nil {
|
||||
return fromFieldError(field.Invalid(path, r.config.Spec.Local.Path, err.Error())), nil
|
||||
return repository.FromFieldError(field.Invalid(path, r.config.Spec.Local.Path, err.Error())), nil
|
||||
}
|
||||
|
||||
_, err = os.Stat(r.path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return fromFieldError(field.NotFound(path, r.config.Spec.Local.Path)), nil
|
||||
return repository.FromFieldError(field.NotFound(path, r.config.Spec.Local.Path)), nil
|
||||
}
|
||||
|
||||
return &provisioning.TestResults{
|
||||
@@ -176,7 +177,7 @@ func (r *localRepository) validateRequest(ref string) error {
|
||||
}
|
||||
|
||||
// ReadResource implements provisioning.Repository.
|
||||
func (r *localRepository) Read(ctx context.Context, filePath string, ref string) (*FileInfo, error) {
|
||||
func (r *localRepository) Read(ctx context.Context, filePath string, ref string) (*repository.FileInfo, error) {
|
||||
if err := r.validateRequest(ref); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -184,13 +185,13 @@ func (r *localRepository) Read(ctx context.Context, filePath string, ref string)
|
||||
actualPath := safepath.Join(r.path, filePath)
|
||||
info, err := os.Stat(actualPath)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, ErrFileNotFound
|
||||
return nil, repository.ErrFileNotFound
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("stat file: %w", err)
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return &FileInfo{
|
||||
return &repository.FileInfo{
|
||||
Path: filePath,
|
||||
Modified: &metav1.Time{
|
||||
Time: info.ModTime(),
|
||||
@@ -209,7 +210,7 @@ func (r *localRepository) Read(ctx context.Context, filePath string, ref string)
|
||||
return nil, fmt.Errorf("calculate hash of file: %w", err)
|
||||
}
|
||||
|
||||
return &FileInfo{
|
||||
return &repository.FileInfo{
|
||||
Path: filePath,
|
||||
Data: data,
|
||||
Hash: hash,
|
||||
@@ -220,7 +221,7 @@ func (r *localRepository) Read(ctx context.Context, filePath string, ref string)
|
||||
}
|
||||
|
||||
// ReadResource implements provisioning.Repository.
|
||||
func (r *localRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) {
|
||||
func (r *localRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
||||
if err := r.validateRequest(ref); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -228,16 +229,16 @@ func (r *localRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeE
|
||||
// Return an empty list when folder does not exist
|
||||
_, err := os.Stat(r.path)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return []FileTreeEntry{}, nil
|
||||
return []repository.FileTreeEntry{}, nil
|
||||
}
|
||||
|
||||
rootlen := len(r.path)
|
||||
entries := make([]FileTreeEntry, 0, 100)
|
||||
entries := make([]repository.FileTreeEntry, 0, 100)
|
||||
err = filepath.Walk(r.path, func(path string, info fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry := FileTreeEntry{
|
||||
entry := repository.FileTreeEntry{
|
||||
Path: strings.TrimLeft(path[rootlen:], "/"),
|
||||
Size: info.Size(),
|
||||
}
|
||||
@@ -265,7 +266,7 @@ func (r *localRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeE
|
||||
func (r *localRepository) calculateFileHash(path string) (string, int64, error) {
|
||||
// Treats https://securego.io/docs/rules/g304.html
|
||||
if !safepath.InDir(path, r.path) {
|
||||
return "", 0, ErrFileNotFound
|
||||
return "", 0, repository.ErrFileNotFound
|
||||
}
|
||||
|
||||
// We've already made sure the path is safe, so we'll ignore the gosec lint.
|
||||
@@ -331,7 +332,7 @@ func (r *localRepository) Update(ctx context.Context, path string, ref string, d
|
||||
|
||||
f, err := os.Stat(path)
|
||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||
return ErrFileNotFound
|
||||
return repository.ErrFileNotFound
|
||||
}
|
||||
if f.IsDir() {
|
||||
return apierrors.NewBadRequest("path exists but it is a directory")
|
||||
+11
-10
@@ -1,4 +1,4 @@
|
||||
package repository
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
)
|
||||
|
||||
func TestLocalResolver(t *testing.T) {
|
||||
@@ -703,7 +704,7 @@ func TestLocalRepository_Update(t *testing.T) {
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
comment: "",
|
||||
expectedErr: ErrFileNotFound,
|
||||
expectedErr: repository.ErrFileNotFound,
|
||||
},
|
||||
{
|
||||
name: "update directory",
|
||||
@@ -1174,7 +1175,7 @@ func TestLocalRepository_Read(t *testing.T) {
|
||||
path string
|
||||
ref string
|
||||
expectedErr error
|
||||
expected *FileInfo
|
||||
expected *repository.FileInfo
|
||||
}{
|
||||
{
|
||||
name: "read existing file",
|
||||
@@ -1203,7 +1204,7 @@ func TestLocalRepository_Read(t *testing.T) {
|
||||
return tempDir, repo
|
||||
},
|
||||
path: "test-file.txt",
|
||||
expected: &FileInfo{
|
||||
expected: &repository.FileInfo{
|
||||
Path: "test-file.txt",
|
||||
Modified: &metav1.Time{Time: time.Now()},
|
||||
Data: []byte("test content"),
|
||||
@@ -1231,7 +1232,7 @@ func TestLocalRepository_Read(t *testing.T) {
|
||||
return tempDir, repo
|
||||
},
|
||||
path: "non-existent-file.txt",
|
||||
expectedErr: ErrFileNotFound,
|
||||
expectedErr: repository.ErrFileNotFound,
|
||||
},
|
||||
{
|
||||
name: "read with ref should fail",
|
||||
@@ -1289,7 +1290,7 @@ func TestLocalRepository_Read(t *testing.T) {
|
||||
return tempDir, repo
|
||||
},
|
||||
path: "test-dir",
|
||||
expected: &FileInfo{
|
||||
expected: &repository.FileInfo{
|
||||
Path: "test-dir",
|
||||
Modified: &metav1.Time{Time: time.Now()},
|
||||
},
|
||||
@@ -1327,7 +1328,7 @@ func TestLocalRepository_ReadTree(t *testing.T) {
|
||||
setup func(t *testing.T) (string, *localRepository)
|
||||
ref string
|
||||
expectedErr error
|
||||
expected []FileTreeEntry
|
||||
expected []repository.FileTreeEntry
|
||||
}{
|
||||
{
|
||||
name: "read empty directory",
|
||||
@@ -1349,7 +1350,7 @@ func TestLocalRepository_ReadTree(t *testing.T) {
|
||||
|
||||
return tempDir, repo
|
||||
},
|
||||
expected: []FileTreeEntry{},
|
||||
expected: []repository.FileTreeEntry{},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
@@ -1379,7 +1380,7 @@ func TestLocalRepository_ReadTree(t *testing.T) {
|
||||
|
||||
return tempDir, repo
|
||||
},
|
||||
expected: []FileTreeEntry{
|
||||
expected: []repository.FileTreeEntry{
|
||||
{Path: "file1.txt", Blob: true, Size: 8},
|
||||
{Path: "file2.txt", Blob: true, Size: 8},
|
||||
{Path: "subdir/", Blob: false},
|
||||
@@ -1432,7 +1433,7 @@ func TestLocalRepository_ReadTree(t *testing.T) {
|
||||
|
||||
return tempDir, repo
|
||||
},
|
||||
expected: []FileTreeEntry{},
|
||||
expected: []repository.FileTreeEntry{},
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package nanogit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
pgh "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
// githubRepository is a repository implementation that integrates both a GitHub API-backed repository and a nanogit-based repository.
|
||||
// It combines the features of the GitHub API with those of a standard Git repository.
|
||||
// This is an interim solution to support both backends within a single repository abstraction.
|
||||
// Once nanogit is fully integrated, functionality from GithubRepository should be migrated here, and this type should extend the nanogit.GitRepository interface.
|
||||
type githubRepository struct {
|
||||
apiRepo repository.GithubRepository
|
||||
nanogitRepo repository.GitRepository
|
||||
}
|
||||
|
||||
func NewGithubRepository(
|
||||
apiRepo repository.GithubRepository,
|
||||
nanogitRepo repository.GitRepository,
|
||||
) repository.GithubRepository {
|
||||
return &githubRepository{
|
||||
apiRepo: apiRepo,
|
||||
nanogitRepo: nanogitRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *githubRepository) Config() *provisioning.Repository {
|
||||
return r.nanogitRepo.Config()
|
||||
}
|
||||
|
||||
func (r *githubRepository) Owner() string {
|
||||
return r.apiRepo.Owner()
|
||||
}
|
||||
|
||||
func (r *githubRepository) Repo() string {
|
||||
return r.apiRepo.Repo()
|
||||
}
|
||||
|
||||
func (r *githubRepository) Client() pgh.Client {
|
||||
return r.apiRepo.Client()
|
||||
}
|
||||
|
||||
// Validate extends the nanogit repo validation with github specific validation
|
||||
func (r *githubRepository) Validate() (list field.ErrorList) {
|
||||
cfg := r.nanogitRepo.Config()
|
||||
gh := cfg.Spec.GitHub
|
||||
if gh == nil {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github"), "a github config is required"))
|
||||
return list
|
||||
}
|
||||
if gh.URL == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github", "url"), "a github url is required"))
|
||||
} else {
|
||||
_, _, err := repository.ParseOwnerRepoGithub(gh.URL)
|
||||
if err != nil {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, err.Error()))
|
||||
} else if !strings.HasPrefix(gh.URL, "https://github.com/") {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, "URL must start with https://github.com/"))
|
||||
}
|
||||
}
|
||||
|
||||
if len(list) > 0 {
|
||||
return list
|
||||
}
|
||||
|
||||
return r.nanogitRepo.Validate()
|
||||
}
|
||||
|
||||
// Test implements provisioning.Repository.
|
||||
func (r *githubRepository) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
||||
return r.apiRepo.Test(ctx)
|
||||
}
|
||||
|
||||
// ReadResource implements provisioning.Repository.
|
||||
func (r *githubRepository) Read(ctx context.Context, filePath, ref string) (*repository.FileInfo, error) {
|
||||
return r.nanogitRepo.Read(ctx, filePath, ref)
|
||||
}
|
||||
|
||||
func (r *githubRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
||||
return r.nanogitRepo.ReadTree(ctx, ref)
|
||||
}
|
||||
|
||||
func (r *githubRepository) Create(ctx context.Context, path, ref string, data []byte, comment string) error {
|
||||
return r.nanogitRepo.Create(ctx, path, ref, data, comment)
|
||||
}
|
||||
|
||||
func (r *githubRepository) Update(ctx context.Context, path, ref string, data []byte, comment string) error {
|
||||
return r.nanogitRepo.Update(ctx, path, ref, data, comment)
|
||||
}
|
||||
|
||||
func (r *githubRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||
return r.nanogitRepo.Write(ctx, path, ref, data, message)
|
||||
}
|
||||
|
||||
func (r *githubRepository) Delete(ctx context.Context, path, ref, comment string) error {
|
||||
return r.nanogitRepo.Delete(ctx, path, ref, comment)
|
||||
}
|
||||
|
||||
func (r *githubRepository) History(ctx context.Context, path, ref string) ([]provisioning.HistoryItem, error) {
|
||||
// Github API provides avatar URLs which nanogit does not, so we delegate to the github repo.
|
||||
return r.apiRepo.History(ctx, path, ref)
|
||||
}
|
||||
|
||||
func (r *githubRepository) LatestRef(ctx context.Context) (string, error) {
|
||||
return r.nanogitRepo.LatestRef(ctx)
|
||||
}
|
||||
|
||||
func (r *githubRepository) CompareFiles(ctx context.Context, base, ref string) ([]repository.VersionedFileChange, error) {
|
||||
return r.nanogitRepo.CompareFiles(ctx, base, ref)
|
||||
}
|
||||
|
||||
// ResourceURLs implements RepositoryWithURLs.
|
||||
func (r *githubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*provisioning.ResourceURLs, error) {
|
||||
return r.apiRepo.ResourceURLs(ctx, file)
|
||||
}
|
||||
|
||||
func (r *githubRepository) Clone(ctx context.Context, opts repository.CloneOptions) (repository.ClonedRepository, error) {
|
||||
return r.nanogitRepo.Clone(ctx, opts)
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
package nanogit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
pgh "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func TestGithubRepository(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
// Create a proper config for testing
|
||||
expectedConfig := &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Type: provisioning.GitHubRepositoryType,
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
URL: "https://github.com/test/repo",
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Set up mock expectations for the methods that exist
|
||||
gitRepo.EXPECT().Config().Return(expectedConfig)
|
||||
apiRepo.EXPECT().Owner().Return("test")
|
||||
apiRepo.EXPECT().Repo().Return("repo")
|
||||
mockClient := pgh.NewMockClient(t)
|
||||
apiRepo.EXPECT().Client().Return(mockClient)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
|
||||
t.Run("delegates config to nanogit repo", func(t *testing.T) {
|
||||
result := repo.Config()
|
||||
require.Equal(t, expectedConfig, result)
|
||||
})
|
||||
|
||||
t.Run("delegates owner to api repo", func(t *testing.T) {
|
||||
result := repo.Owner()
|
||||
require.Equal(t, "test", result)
|
||||
})
|
||||
|
||||
t.Run("delegates repo to api repo", func(t *testing.T) {
|
||||
result := repo.Repo()
|
||||
require.Equal(t, "repo", result)
|
||||
})
|
||||
|
||||
t.Run("delegates client to api repo", func(t *testing.T) {
|
||||
result := repo.Client()
|
||||
require.Equal(t, mockClient, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGithubRepositoryDelegation(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("delegates test to api repo", func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
expectedResult := &provisioning.TestResults{
|
||||
Code: 200,
|
||||
Success: true,
|
||||
}
|
||||
|
||||
apiRepo.EXPECT().Test(ctx).Return(expectedResult, nil)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
result, err := repo.Test(ctx)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedResult, result)
|
||||
})
|
||||
|
||||
t.Run("delegates read to nanogit repo", func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
expectedFileInfo := &repository.FileInfo{
|
||||
Path: "test.yaml",
|
||||
Data: []byte("test data"),
|
||||
Ref: "main",
|
||||
Hash: "abc123",
|
||||
}
|
||||
|
||||
gitRepo.EXPECT().Read(ctx, "test.yaml", "main").Return(expectedFileInfo, nil)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
result, err := repo.Read(ctx, "test.yaml", "main")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedFileInfo, result)
|
||||
})
|
||||
|
||||
t.Run("delegates read tree to nanogit repo", func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
expectedEntries := []repository.FileTreeEntry{
|
||||
{Path: "file1.yaml", Size: 100, Hash: "hash1", Blob: true},
|
||||
{Path: "dir/", Size: 0, Hash: "hash2", Blob: false},
|
||||
}
|
||||
|
||||
gitRepo.EXPECT().ReadTree(ctx, "main").Return(expectedEntries, nil)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
result, err := repo.ReadTree(ctx, "main")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedEntries, result)
|
||||
})
|
||||
|
||||
t.Run("delegates create to nanogit repo", func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
data := []byte("test content")
|
||||
gitRepo.EXPECT().Create(ctx, "new-file.yaml", "main", data, "Create new file").Return(nil)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
err := repo.Create(ctx, "new-file.yaml", "main", data, "Create new file")
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("delegates update to nanogit repo", func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
data := []byte("updated content")
|
||||
gitRepo.EXPECT().Update(ctx, "existing-file.yaml", "main", data, "Update file").Return(nil)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
err := repo.Update(ctx, "existing-file.yaml", "main", data, "Update file")
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("delegates write to nanogit repo", func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
data := []byte("file content")
|
||||
gitRepo.EXPECT().Write(ctx, "file.yaml", "main", data, "Write file").Return(nil)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
err := repo.Write(ctx, "file.yaml", "main", data, "Write file")
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("delegates delete to nanogit repo", func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
gitRepo.EXPECT().Delete(ctx, "file.yaml", "main", "Delete file").Return(nil)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
err := repo.Delete(ctx, "file.yaml", "main", "Delete file")
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("delegates history to api repo", func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
expectedHistory := []provisioning.HistoryItem{
|
||||
{
|
||||
Ref: "commit1",
|
||||
Message: "First commit",
|
||||
Authors: []provisioning.Author{{Name: "Test User"}},
|
||||
},
|
||||
}
|
||||
|
||||
apiRepo.EXPECT().History(ctx, "file.yaml", "main").Return(expectedHistory, nil)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
result, err := repo.History(ctx, "file.yaml", "main")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedHistory, result)
|
||||
})
|
||||
|
||||
t.Run("delegates latest ref to nanogit repo", func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
expectedRef := "abc123def456"
|
||||
gitRepo.EXPECT().LatestRef(ctx).Return(expectedRef, nil)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
result, err := repo.LatestRef(ctx)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedRef, result)
|
||||
})
|
||||
|
||||
t.Run("delegates compare files to nanogit repo", func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
expectedChanges := []repository.VersionedFileChange{
|
||||
{
|
||||
Action: repository.FileActionCreated,
|
||||
Path: "new-file.yaml",
|
||||
Ref: "feature-branch",
|
||||
},
|
||||
}
|
||||
|
||||
gitRepo.EXPECT().CompareFiles(ctx, "main", "feature-branch").Return(expectedChanges, nil)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
result, err := repo.CompareFiles(ctx, "main", "feature-branch")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedChanges, result)
|
||||
})
|
||||
|
||||
t.Run("delegates resource URLs to api repo", func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
fileInfo := &repository.FileInfo{
|
||||
Path: "dashboard.json",
|
||||
Ref: "main",
|
||||
Hash: "hash123",
|
||||
}
|
||||
|
||||
expectedURLs := &provisioning.ResourceURLs{
|
||||
SourceURL: "https://github.com/test/repo/blob/main/dashboard.json",
|
||||
RepositoryURL: "https://github.com/test/repo",
|
||||
NewPullRequestURL: "https://github.com/test/repo/compare/main...feature",
|
||||
}
|
||||
|
||||
apiRepo.EXPECT().ResourceURLs(ctx, fileInfo).Return(expectedURLs, nil)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
result, err := repo.ResourceURLs(ctx, fileInfo)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedURLs, result)
|
||||
})
|
||||
|
||||
t.Run("delegates clone to nanogit repo", func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
mockClonedRepo := repository.NewMockClonedRepository(t)
|
||||
|
||||
opts := repository.CloneOptions{
|
||||
CreateIfNotExists: true,
|
||||
PushOnWrites: true,
|
||||
}
|
||||
|
||||
gitRepo.EXPECT().Clone(ctx, opts).Return(mockClonedRepo, nil)
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
result, err := repo.Clone(ctx, opts)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, mockClonedRepo, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGithubRepositoryValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *provisioning.Repository
|
||||
expectedErrors int
|
||||
}{
|
||||
{
|
||||
name: "missing github config",
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Type: provisioning.GitHubRepositoryType,
|
||||
},
|
||||
},
|
||||
expectedErrors: 1,
|
||||
},
|
||||
{
|
||||
name: "missing github url",
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Type: provisioning.GitHubRepositoryType,
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 1,
|
||||
},
|
||||
{
|
||||
name: "invalid github url",
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Type: provisioning.GitHubRepositoryType,
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
URL: "invalid-url",
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 1,
|
||||
},
|
||||
{
|
||||
name: "non-github url",
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Type: provisioning.GitHubRepositoryType,
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
URL: "https://gitlab.com/test/repo",
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 1,
|
||||
},
|
||||
{
|
||||
name: "valid github config",
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Type: provisioning.GitHubRepositoryType,
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
URL: "https://github.com/test/repo",
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
apiRepo := repository.NewMockGithubRepository(t)
|
||||
gitRepo := repository.NewMockGitRepository(t)
|
||||
|
||||
// Set up mock expectations
|
||||
gitRepo.EXPECT().Config().Return(tt.config)
|
||||
if tt.expectedErrors == 0 {
|
||||
// If no validation errors expected, nanogit validation should be called
|
||||
gitRepo.EXPECT().Validate().Return(field.ErrorList{})
|
||||
}
|
||||
|
||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
||||
|
||||
result := repo.Validate()
|
||||
require.Len(t, result, tt.expectedErrors)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package repository
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -67,47 +65,6 @@ type FileInfo struct {
|
||||
Modified *metav1.Time
|
||||
}
|
||||
|
||||
//go:generate mockery --name CloneFn --structname MockCloneFn --inpackage --filename clone_fn_mock.go --with-expecter
|
||||
type CloneFn func(ctx context.Context, opts CloneOptions) (ClonedRepository, error)
|
||||
|
||||
type CloneOptions struct {
|
||||
// If the branch does not exist, create it
|
||||
CreateIfNotExists bool
|
||||
|
||||
// Push on every write
|
||||
PushOnWrites bool
|
||||
|
||||
// Maximum allowed size for repository clone in bytes (0 means no limit)
|
||||
MaxSize int64
|
||||
|
||||
// Maximum time allowed for clone operation in seconds (0 means no limit)
|
||||
Timeout time.Duration
|
||||
|
||||
// Progress is the writer to report progress to
|
||||
Progress io.Writer
|
||||
|
||||
// BeforeFn is called before the clone operation starts
|
||||
BeforeFn func() error
|
||||
}
|
||||
|
||||
//go:generate mockery --name ClonableRepository --structname MockClonableRepository --inpackage --filename clonable_repository_mock.go --with-expecter
|
||||
type ClonableRepository interface {
|
||||
Clone(ctx context.Context, opts CloneOptions) (ClonedRepository, error)
|
||||
}
|
||||
|
||||
type PushOptions struct {
|
||||
Timeout time.Duration
|
||||
Progress io.Writer
|
||||
BeforeFn func() error
|
||||
}
|
||||
|
||||
//go:generate mockery --name ClonedRepository --structname MockClonedRepository --inpackage --filename cloned_repository_mock.go --with-expecter
|
||||
type ClonedRepository interface {
|
||||
ReaderWriter
|
||||
Push(ctx context.Context, opts PushOptions) error
|
||||
Remove(ctx context.Context) error
|
||||
}
|
||||
|
||||
// An entry in the file tree, as returned by 'ReadFileTree'. Like FileInfo, but contains less information.
|
||||
type FileTreeEntry struct {
|
||||
// The path to the file from the base path given (if any).
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockStageableRepository is an autogenerated mock type for the StageableRepository type
|
||||
type MockStageableRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockStageableRepository_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockStageableRepository) EXPECT() *MockStageableRepository_Expecter {
|
||||
return &MockStageableRepository_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Stage provides a mock function with given fields: ctx, opts
|
||||
func (_m *MockStageableRepository) Stage(ctx context.Context, opts StageOptions) (StagedRepository, error) {
|
||||
ret := _m.Called(ctx, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Stage")
|
||||
}
|
||||
|
||||
var r0 StagedRepository
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, StageOptions) (StagedRepository, error)); ok {
|
||||
return rf(ctx, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, StageOptions) StagedRepository); ok {
|
||||
r0 = rf(ctx, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(StagedRepository)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, StageOptions) error); ok {
|
||||
r1 = rf(ctx, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockStageableRepository_Stage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stage'
|
||||
type MockStageableRepository_Stage_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Stage is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - opts StageOptions
|
||||
func (_e *MockStageableRepository_Expecter) Stage(ctx interface{}, opts interface{}) *MockStageableRepository_Stage_Call {
|
||||
return &MockStageableRepository_Stage_Call{Call: _e.mock.On("Stage", ctx, opts)}
|
||||
}
|
||||
|
||||
func (_c *MockStageableRepository_Stage_Call) Run(run func(ctx context.Context, opts StageOptions)) *MockStageableRepository_Stage_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(StageOptions))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockStageableRepository_Stage_Call) Return(_a0 StagedRepository, _a1 error) *MockStageableRepository_Stage_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockStageableRepository_Stage_Call) RunAndReturn(run func(context.Context, StageOptions) (StagedRepository, error)) *MockStageableRepository_Stage_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockStageableRepository creates a new instance of MockStageableRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockStageableRepository(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockStageableRepository {
|
||||
mock := &MockStageableRepository{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
"github.com/grafana/nanogit"
|
||||
)
|
||||
|
||||
type StageOptions struct {
|
||||
// Push on every write
|
||||
PushOnWrites bool
|
||||
// Maximum time allowed for clone operation in seconds (0 means no limit)
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
//go:generate mockery --name StageableRepository --structname MockStageableRepository --inpackage --filename stageable_repository_mock.go --with-expecter
|
||||
type StageableRepository interface {
|
||||
Stage(ctx context.Context, opts StageOptions) (StagedRepository, error)
|
||||
}
|
||||
|
||||
//go:generate mockery --name StagedRepository --structname MockStagedRepository --inpackage --filename staged_repository_mock.go --with-expecter
|
||||
type StagedRepository interface {
|
||||
ReaderWriter
|
||||
Push(ctx context.Context) error
|
||||
Remove(ctx context.Context) error
|
||||
}
|
||||
|
||||
// WrapWithStageAndPushIfPossible attempts to stage the given repository. If staging is supported,
|
||||
// it runs the provided function on the staged repository, then pushes any changes and cleans up the staged repository.
|
||||
// If staging is not supported, it runs the function on the original repository without pushing.
|
||||
// The 'staged' argument to the function indicates whether a staged repository was used.
|
||||
func WrapWithStageAndPushIfPossible(
|
||||
ctx context.Context,
|
||||
repo Repository,
|
||||
stageOptions StageOptions,
|
||||
fn func(repo Repository, staged bool) error,
|
||||
) error {
|
||||
stageable, ok := repo.(StageableRepository)
|
||||
if !ok {
|
||||
return fn(repo, false)
|
||||
}
|
||||
|
||||
staged, err := stageable.Stage(ctx, stageOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stage repository: %w", err)
|
||||
}
|
||||
|
||||
// We don't, we simply log it
|
||||
// FIXME: should we handle this differently?
|
||||
defer func() {
|
||||
if err := staged.Remove(ctx); err != nil {
|
||||
logging.FromContext(ctx).Error("failed to remove staged repository after export", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := fn(staged, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = staged.Push(ctx); err != nil {
|
||||
if errors.Is(err, nanogit.ErrNothingToPush) {
|
||||
return nil // OK, already pushed
|
||||
}
|
||||
return fmt.Errorf("wrapped push error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+103
-104
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package repository
|
||||
|
||||
@@ -11,21 +11,21 @@ import (
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
// MockClonedRepository is an autogenerated mock type for the ClonedRepository type
|
||||
type MockClonedRepository struct {
|
||||
// MockStagedRepository is an autogenerated mock type for the StagedRepository type
|
||||
type MockStagedRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockClonedRepository_Expecter struct {
|
||||
type MockStagedRepository_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockClonedRepository) EXPECT() *MockClonedRepository_Expecter {
|
||||
return &MockClonedRepository_Expecter{mock: &_m.Mock}
|
||||
func (_m *MockStagedRepository) EXPECT() *MockStagedRepository_Expecter {
|
||||
return &MockStagedRepository_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Config provides a mock function with no fields
|
||||
func (_m *MockClonedRepository) Config() *v0alpha1.Repository {
|
||||
func (_m *MockStagedRepository) Config() *v0alpha1.Repository {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -44,35 +44,35 @@ func (_m *MockClonedRepository) Config() *v0alpha1.Repository {
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClonedRepository_Config_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Config'
|
||||
type MockClonedRepository_Config_Call struct {
|
||||
// MockStagedRepository_Config_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Config'
|
||||
type MockStagedRepository_Config_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Config is a helper method to define mock.On call
|
||||
func (_e *MockClonedRepository_Expecter) Config() *MockClonedRepository_Config_Call {
|
||||
return &MockClonedRepository_Config_Call{Call: _e.mock.On("Config")}
|
||||
func (_e *MockStagedRepository_Expecter) Config() *MockStagedRepository_Config_Call {
|
||||
return &MockStagedRepository_Config_Call{Call: _e.mock.On("Config")}
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Config_Call) Run(run func()) *MockClonedRepository_Config_Call {
|
||||
func (_c *MockStagedRepository_Config_Call) Run(run func()) *MockStagedRepository_Config_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Config_Call) Return(_a0 *v0alpha1.Repository) *MockClonedRepository_Config_Call {
|
||||
func (_c *MockStagedRepository_Config_Call) Return(_a0 *v0alpha1.Repository) *MockStagedRepository_Config_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Config_Call) RunAndReturn(run func() *v0alpha1.Repository) *MockClonedRepository_Config_Call {
|
||||
func (_c *MockStagedRepository_Config_Call) RunAndReturn(run func() *v0alpha1.Repository) *MockStagedRepository_Config_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, path, ref, data, message
|
||||
func (_m *MockClonedRepository) Create(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||
func (_m *MockStagedRepository) Create(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||
ret := _m.Called(ctx, path, ref, data, message)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -89,8 +89,8 @@ func (_m *MockClonedRepository) Create(ctx context.Context, path string, ref str
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClonedRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
|
||||
type MockClonedRepository_Create_Call struct {
|
||||
// MockStagedRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
|
||||
type MockStagedRepository_Create_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
@@ -100,29 +100,29 @@ type MockClonedRepository_Create_Call struct {
|
||||
// - ref string
|
||||
// - data []byte
|
||||
// - message string
|
||||
func (_e *MockClonedRepository_Expecter) Create(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockClonedRepository_Create_Call {
|
||||
return &MockClonedRepository_Create_Call{Call: _e.mock.On("Create", ctx, path, ref, data, message)}
|
||||
func (_e *MockStagedRepository_Expecter) Create(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockStagedRepository_Create_Call {
|
||||
return &MockStagedRepository_Create_Call{Call: _e.mock.On("Create", ctx, path, ref, data, message)}
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Create_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockClonedRepository_Create_Call {
|
||||
func (_c *MockStagedRepository_Create_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockStagedRepository_Create_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Create_Call) Return(_a0 error) *MockClonedRepository_Create_Call {
|
||||
func (_c *MockStagedRepository_Create_Call) Return(_a0 error) *MockStagedRepository_Create_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Create_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockClonedRepository_Create_Call {
|
||||
func (_c *MockStagedRepository_Create_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockStagedRepository_Create_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, path, ref, message
|
||||
func (_m *MockClonedRepository) Delete(ctx context.Context, path string, ref string, message string) error {
|
||||
func (_m *MockStagedRepository) Delete(ctx context.Context, path string, ref string, message string) error {
|
||||
ret := _m.Called(ctx, path, ref, message)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -139,8 +139,8 @@ func (_m *MockClonedRepository) Delete(ctx context.Context, path string, ref str
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClonedRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
|
||||
type MockClonedRepository_Delete_Call struct {
|
||||
// MockStagedRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
|
||||
type MockStagedRepository_Delete_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
@@ -149,38 +149,38 @@ type MockClonedRepository_Delete_Call struct {
|
||||
// - path string
|
||||
// - ref string
|
||||
// - message string
|
||||
func (_e *MockClonedRepository_Expecter) Delete(ctx interface{}, path interface{}, ref interface{}, message interface{}) *MockClonedRepository_Delete_Call {
|
||||
return &MockClonedRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, path, ref, message)}
|
||||
func (_e *MockStagedRepository_Expecter) Delete(ctx interface{}, path interface{}, ref interface{}, message interface{}) *MockStagedRepository_Delete_Call {
|
||||
return &MockStagedRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, path, ref, message)}
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Delete_Call) Run(run func(ctx context.Context, path string, ref string, message string)) *MockClonedRepository_Delete_Call {
|
||||
func (_c *MockStagedRepository_Delete_Call) Run(run func(ctx context.Context, path string, ref string, message string)) *MockStagedRepository_Delete_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Delete_Call) Return(_a0 error) *MockClonedRepository_Delete_Call {
|
||||
func (_c *MockStagedRepository_Delete_Call) Return(_a0 error) *MockStagedRepository_Delete_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Delete_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockClonedRepository_Delete_Call {
|
||||
func (_c *MockStagedRepository_Delete_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockStagedRepository_Delete_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Push provides a mock function with given fields: ctx, opts
|
||||
func (_m *MockClonedRepository) Push(ctx context.Context, opts PushOptions) error {
|
||||
ret := _m.Called(ctx, opts)
|
||||
// Push provides a mock function with given fields: ctx
|
||||
func (_m *MockStagedRepository) Push(ctx context.Context) error {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Push")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, PushOptions) error); ok {
|
||||
r0 = rf(ctx, opts)
|
||||
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
@@ -188,37 +188,36 @@ func (_m *MockClonedRepository) Push(ctx context.Context, opts PushOptions) erro
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClonedRepository_Push_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Push'
|
||||
type MockClonedRepository_Push_Call struct {
|
||||
// MockStagedRepository_Push_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Push'
|
||||
type MockStagedRepository_Push_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Push is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - opts PushOptions
|
||||
func (_e *MockClonedRepository_Expecter) Push(ctx interface{}, opts interface{}) *MockClonedRepository_Push_Call {
|
||||
return &MockClonedRepository_Push_Call{Call: _e.mock.On("Push", ctx, opts)}
|
||||
func (_e *MockStagedRepository_Expecter) Push(ctx interface{}) *MockStagedRepository_Push_Call {
|
||||
return &MockStagedRepository_Push_Call{Call: _e.mock.On("Push", ctx)}
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Push_Call) Run(run func(ctx context.Context, opts PushOptions)) *MockClonedRepository_Push_Call {
|
||||
func (_c *MockStagedRepository_Push_Call) Run(run func(ctx context.Context)) *MockStagedRepository_Push_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(PushOptions))
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Push_Call) Return(_a0 error) *MockClonedRepository_Push_Call {
|
||||
func (_c *MockStagedRepository_Push_Call) Return(_a0 error) *MockStagedRepository_Push_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Push_Call) RunAndReturn(run func(context.Context, PushOptions) error) *MockClonedRepository_Push_Call {
|
||||
func (_c *MockStagedRepository_Push_Call) RunAndReturn(run func(context.Context) error) *MockStagedRepository_Push_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Read provides a mock function with given fields: ctx, path, ref
|
||||
func (_m *MockClonedRepository) Read(ctx context.Context, path string, ref string) (*FileInfo, error) {
|
||||
func (_m *MockStagedRepository) Read(ctx context.Context, path string, ref string) (*FileInfo, error) {
|
||||
ret := _m.Called(ctx, path, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -247,8 +246,8 @@ func (_m *MockClonedRepository) Read(ctx context.Context, path string, ref strin
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClonedRepository_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read'
|
||||
type MockClonedRepository_Read_Call struct {
|
||||
// MockStagedRepository_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read'
|
||||
type MockStagedRepository_Read_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
@@ -256,29 +255,29 @@ type MockClonedRepository_Read_Call struct {
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
func (_e *MockClonedRepository_Expecter) Read(ctx interface{}, path interface{}, ref interface{}) *MockClonedRepository_Read_Call {
|
||||
return &MockClonedRepository_Read_Call{Call: _e.mock.On("Read", ctx, path, ref)}
|
||||
func (_e *MockStagedRepository_Expecter) Read(ctx interface{}, path interface{}, ref interface{}) *MockStagedRepository_Read_Call {
|
||||
return &MockStagedRepository_Read_Call{Call: _e.mock.On("Read", ctx, path, ref)}
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Read_Call) Run(run func(ctx context.Context, path string, ref string)) *MockClonedRepository_Read_Call {
|
||||
func (_c *MockStagedRepository_Read_Call) Run(run func(ctx context.Context, path string, ref string)) *MockStagedRepository_Read_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Read_Call) Return(_a0 *FileInfo, _a1 error) *MockClonedRepository_Read_Call {
|
||||
func (_c *MockStagedRepository_Read_Call) Return(_a0 *FileInfo, _a1 error) *MockStagedRepository_Read_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*FileInfo, error)) *MockClonedRepository_Read_Call {
|
||||
func (_c *MockStagedRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*FileInfo, error)) *MockStagedRepository_Read_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ReadTree provides a mock function with given fields: ctx, ref
|
||||
func (_m *MockClonedRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) {
|
||||
func (_m *MockStagedRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) {
|
||||
ret := _m.Called(ctx, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -307,37 +306,37 @@ func (_m *MockClonedRepository) ReadTree(ctx context.Context, ref string) ([]Fil
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClonedRepository_ReadTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadTree'
|
||||
type MockClonedRepository_ReadTree_Call struct {
|
||||
// MockStagedRepository_ReadTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadTree'
|
||||
type MockStagedRepository_ReadTree_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ReadTree is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - ref string
|
||||
func (_e *MockClonedRepository_Expecter) ReadTree(ctx interface{}, ref interface{}) *MockClonedRepository_ReadTree_Call {
|
||||
return &MockClonedRepository_ReadTree_Call{Call: _e.mock.On("ReadTree", ctx, ref)}
|
||||
func (_e *MockStagedRepository_Expecter) ReadTree(ctx interface{}, ref interface{}) *MockStagedRepository_ReadTree_Call {
|
||||
return &MockStagedRepository_ReadTree_Call{Call: _e.mock.On("ReadTree", ctx, ref)}
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_ReadTree_Call) Run(run func(ctx context.Context, ref string)) *MockClonedRepository_ReadTree_Call {
|
||||
func (_c *MockStagedRepository_ReadTree_Call) Run(run func(ctx context.Context, ref string)) *MockStagedRepository_ReadTree_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_ReadTree_Call) Return(_a0 []FileTreeEntry, _a1 error) *MockClonedRepository_ReadTree_Call {
|
||||
func (_c *MockStagedRepository_ReadTree_Call) Return(_a0 []FileTreeEntry, _a1 error) *MockStagedRepository_ReadTree_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]FileTreeEntry, error)) *MockClonedRepository_ReadTree_Call {
|
||||
func (_c *MockStagedRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]FileTreeEntry, error)) *MockStagedRepository_ReadTree_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Remove provides a mock function with given fields: ctx
|
||||
func (_m *MockClonedRepository) Remove(ctx context.Context) error {
|
||||
func (_m *MockStagedRepository) Remove(ctx context.Context) error {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -354,36 +353,36 @@ func (_m *MockClonedRepository) Remove(ctx context.Context) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClonedRepository_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove'
|
||||
type MockClonedRepository_Remove_Call struct {
|
||||
// MockStagedRepository_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove'
|
||||
type MockStagedRepository_Remove_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Remove is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockClonedRepository_Expecter) Remove(ctx interface{}) *MockClonedRepository_Remove_Call {
|
||||
return &MockClonedRepository_Remove_Call{Call: _e.mock.On("Remove", ctx)}
|
||||
func (_e *MockStagedRepository_Expecter) Remove(ctx interface{}) *MockStagedRepository_Remove_Call {
|
||||
return &MockStagedRepository_Remove_Call{Call: _e.mock.On("Remove", ctx)}
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Remove_Call) Run(run func(ctx context.Context)) *MockClonedRepository_Remove_Call {
|
||||
func (_c *MockStagedRepository_Remove_Call) Run(run func(ctx context.Context)) *MockStagedRepository_Remove_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Remove_Call) Return(_a0 error) *MockClonedRepository_Remove_Call {
|
||||
func (_c *MockStagedRepository_Remove_Call) Return(_a0 error) *MockStagedRepository_Remove_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Remove_Call) RunAndReturn(run func(context.Context) error) *MockClonedRepository_Remove_Call {
|
||||
func (_c *MockStagedRepository_Remove_Call) RunAndReturn(run func(context.Context) error) *MockStagedRepository_Remove_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Test provides a mock function with given fields: ctx
|
||||
func (_m *MockClonedRepository) Test(ctx context.Context) (*v0alpha1.TestResults, error) {
|
||||
func (_m *MockStagedRepository) Test(ctx context.Context) (*v0alpha1.TestResults, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -412,36 +411,36 @@ func (_m *MockClonedRepository) Test(ctx context.Context) (*v0alpha1.TestResults
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClonedRepository_Test_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Test'
|
||||
type MockClonedRepository_Test_Call struct {
|
||||
// MockStagedRepository_Test_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Test'
|
||||
type MockStagedRepository_Test_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Test is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockClonedRepository_Expecter) Test(ctx interface{}) *MockClonedRepository_Test_Call {
|
||||
return &MockClonedRepository_Test_Call{Call: _e.mock.On("Test", ctx)}
|
||||
func (_e *MockStagedRepository_Expecter) Test(ctx interface{}) *MockStagedRepository_Test_Call {
|
||||
return &MockStagedRepository_Test_Call{Call: _e.mock.On("Test", ctx)}
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Test_Call) Run(run func(ctx context.Context)) *MockClonedRepository_Test_Call {
|
||||
func (_c *MockStagedRepository_Test_Call) Run(run func(ctx context.Context)) *MockStagedRepository_Test_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Test_Call) Return(_a0 *v0alpha1.TestResults, _a1 error) *MockClonedRepository_Test_Call {
|
||||
func (_c *MockStagedRepository_Test_Call) Return(_a0 *v0alpha1.TestResults, _a1 error) *MockStagedRepository_Test_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Test_Call) RunAndReturn(run func(context.Context) (*v0alpha1.TestResults, error)) *MockClonedRepository_Test_Call {
|
||||
func (_c *MockStagedRepository_Test_Call) RunAndReturn(run func(context.Context) (*v0alpha1.TestResults, error)) *MockStagedRepository_Test_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: ctx, path, ref, data, message
|
||||
func (_m *MockClonedRepository) Update(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||
func (_m *MockStagedRepository) Update(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||
ret := _m.Called(ctx, path, ref, data, message)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -458,8 +457,8 @@ func (_m *MockClonedRepository) Update(ctx context.Context, path string, ref str
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClonedRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
|
||||
type MockClonedRepository_Update_Call struct {
|
||||
// MockStagedRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
|
||||
type MockStagedRepository_Update_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
@@ -469,29 +468,29 @@ type MockClonedRepository_Update_Call struct {
|
||||
// - ref string
|
||||
// - data []byte
|
||||
// - message string
|
||||
func (_e *MockClonedRepository_Expecter) Update(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockClonedRepository_Update_Call {
|
||||
return &MockClonedRepository_Update_Call{Call: _e.mock.On("Update", ctx, path, ref, data, message)}
|
||||
func (_e *MockStagedRepository_Expecter) Update(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockStagedRepository_Update_Call {
|
||||
return &MockStagedRepository_Update_Call{Call: _e.mock.On("Update", ctx, path, ref, data, message)}
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Update_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockClonedRepository_Update_Call {
|
||||
func (_c *MockStagedRepository_Update_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockStagedRepository_Update_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Update_Call) Return(_a0 error) *MockClonedRepository_Update_Call {
|
||||
func (_c *MockStagedRepository_Update_Call) Return(_a0 error) *MockStagedRepository_Update_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Update_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockClonedRepository_Update_Call {
|
||||
func (_c *MockStagedRepository_Update_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockStagedRepository_Update_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Validate provides a mock function with no fields
|
||||
func (_m *MockClonedRepository) Validate() field.ErrorList {
|
||||
func (_m *MockStagedRepository) Validate() field.ErrorList {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -510,35 +509,35 @@ func (_m *MockClonedRepository) Validate() field.ErrorList {
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClonedRepository_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
|
||||
type MockClonedRepository_Validate_Call struct {
|
||||
// MockStagedRepository_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
|
||||
type MockStagedRepository_Validate_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Validate is a helper method to define mock.On call
|
||||
func (_e *MockClonedRepository_Expecter) Validate() *MockClonedRepository_Validate_Call {
|
||||
return &MockClonedRepository_Validate_Call{Call: _e.mock.On("Validate")}
|
||||
func (_e *MockStagedRepository_Expecter) Validate() *MockStagedRepository_Validate_Call {
|
||||
return &MockStagedRepository_Validate_Call{Call: _e.mock.On("Validate")}
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Validate_Call) Run(run func()) *MockClonedRepository_Validate_Call {
|
||||
func (_c *MockStagedRepository_Validate_Call) Run(run func()) *MockStagedRepository_Validate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Validate_Call) Return(_a0 field.ErrorList) *MockClonedRepository_Validate_Call {
|
||||
func (_c *MockStagedRepository_Validate_Call) Return(_a0 field.ErrorList) *MockStagedRepository_Validate_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Validate_Call) RunAndReturn(run func() field.ErrorList) *MockClonedRepository_Validate_Call {
|
||||
func (_c *MockStagedRepository_Validate_Call) RunAndReturn(run func() field.ErrorList) *MockStagedRepository_Validate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Write provides a mock function with given fields: ctx, path, ref, data, message
|
||||
func (_m *MockClonedRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||
func (_m *MockStagedRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||
ret := _m.Called(ctx, path, ref, data, message)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -555,8 +554,8 @@ func (_m *MockClonedRepository) Write(ctx context.Context, path string, ref stri
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClonedRepository_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write'
|
||||
type MockClonedRepository_Write_Call struct {
|
||||
// MockStagedRepository_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write'
|
||||
type MockStagedRepository_Write_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
@@ -566,34 +565,34 @@ type MockClonedRepository_Write_Call struct {
|
||||
// - ref string
|
||||
// - data []byte
|
||||
// - message string
|
||||
func (_e *MockClonedRepository_Expecter) Write(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockClonedRepository_Write_Call {
|
||||
return &MockClonedRepository_Write_Call{Call: _e.mock.On("Write", ctx, path, ref, data, message)}
|
||||
func (_e *MockStagedRepository_Expecter) Write(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockStagedRepository_Write_Call {
|
||||
return &MockStagedRepository_Write_Call{Call: _e.mock.On("Write", ctx, path, ref, data, message)}
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Write_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockClonedRepository_Write_Call {
|
||||
func (_c *MockStagedRepository_Write_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockStagedRepository_Write_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Write_Call) Return(_a0 error) *MockClonedRepository_Write_Call {
|
||||
func (_c *MockStagedRepository_Write_Call) Return(_a0 error) *MockStagedRepository_Write_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClonedRepository_Write_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockClonedRepository_Write_Call {
|
||||
func (_c *MockStagedRepository_Write_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockStagedRepository_Write_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockClonedRepository creates a new instance of MockClonedRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// NewMockStagedRepository creates a new instance of MockStagedRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockClonedRepository(t interface {
|
||||
func NewMockStagedRepository(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockClonedRepository {
|
||||
mock := &MockClonedRepository{}
|
||||
}) *MockStagedRepository {
|
||||
mock := &MockStagedRepository{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
@@ -0,0 +1,144 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type mockStagedRepo struct {
|
||||
*MockStageableRepository
|
||||
*MockStagedRepository
|
||||
}
|
||||
|
||||
func Test_WrapWithStageAndPushIfPossible_NonStageableRepository(t *testing.T) {
|
||||
nonStageable := NewMockRepository(t)
|
||||
var called bool
|
||||
fn := func(repo Repository, staged bool) error {
|
||||
called = true
|
||||
return errors.New("operation failed")
|
||||
}
|
||||
|
||||
err := WrapWithStageAndPushIfPossible(context.Background(), nonStageable, StageOptions{}, fn)
|
||||
require.EqualError(t, err, "operation failed")
|
||||
require.True(t, called)
|
||||
}
|
||||
|
||||
func TestWrapWithStageAndPushIfPossible(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMocks func(t *testing.T) *mockStagedRepo
|
||||
operation func(repo Repository, staged bool) error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "successful stage, operation, and push",
|
||||
setupMocks: func(t *testing.T) *mockStagedRepo {
|
||||
mockRepo := NewMockStageableRepository(t)
|
||||
mockStaged := NewMockStagedRepository(t)
|
||||
|
||||
mockRepo.EXPECT().Stage(mock.Anything, StageOptions{}).Return(mockStaged, nil)
|
||||
mockStaged.EXPECT().Push(mock.Anything).Return(nil)
|
||||
mockStaged.EXPECT().Remove(mock.Anything).Return(nil)
|
||||
return &mockStagedRepo{
|
||||
MockStageableRepository: mockRepo,
|
||||
MockStagedRepository: mockStaged,
|
||||
}
|
||||
},
|
||||
operation: func(repo Repository, staged bool) error {
|
||||
require.True(t, staged)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stage failure",
|
||||
setupMocks: func(t *testing.T) *mockStagedRepo {
|
||||
mockRepo := NewMockStageableRepository(t)
|
||||
|
||||
mockRepo.EXPECT().Stage(mock.Anything, StageOptions{}).Return(nil, errors.New("stage failed"))
|
||||
|
||||
return &mockStagedRepo{
|
||||
MockStageableRepository: mockRepo,
|
||||
}
|
||||
},
|
||||
operation: func(repo Repository, staged bool) error {
|
||||
return nil
|
||||
},
|
||||
expectedError: "stage repository: stage failed",
|
||||
},
|
||||
{
|
||||
name: "operation failure",
|
||||
setupMocks: func(t *testing.T) *mockStagedRepo {
|
||||
mockRepo := NewMockStageableRepository(t)
|
||||
mockStaged := NewMockStagedRepository(t)
|
||||
|
||||
mockRepo.EXPECT().Stage(mock.Anything, StageOptions{}).Return(mockStaged, nil)
|
||||
mockStaged.EXPECT().Remove(mock.Anything).Return(nil)
|
||||
|
||||
return &mockStagedRepo{
|
||||
MockStageableRepository: mockRepo,
|
||||
MockStagedRepository: mockStaged,
|
||||
}
|
||||
},
|
||||
operation: func(repo Repository, staged bool) error {
|
||||
return errors.New("operation failed")
|
||||
},
|
||||
expectedError: "operation failed",
|
||||
},
|
||||
{
|
||||
name: "push failure",
|
||||
setupMocks: func(t *testing.T) *mockStagedRepo {
|
||||
mockRepo := NewMockStageableRepository(t)
|
||||
mockStaged := NewMockStagedRepository(t)
|
||||
|
||||
mockRepo.EXPECT().Stage(mock.Anything, StageOptions{}).Return(mockStaged, nil)
|
||||
mockStaged.EXPECT().Push(mock.Anything).Return(errors.New("push failed"))
|
||||
mockStaged.EXPECT().Remove(mock.Anything).Return(nil)
|
||||
|
||||
return &mockStagedRepo{
|
||||
MockStageableRepository: mockRepo,
|
||||
MockStagedRepository: mockStaged,
|
||||
}
|
||||
},
|
||||
operation: func(repo Repository, staged bool) error {
|
||||
return nil
|
||||
},
|
||||
expectedError: "wrapped push error: push failed",
|
||||
},
|
||||
{
|
||||
name: "remove failure should only log",
|
||||
setupMocks: func(t *testing.T) *mockStagedRepo {
|
||||
mockRepo := NewMockStageableRepository(t)
|
||||
mockStaged := NewMockStagedRepository(t)
|
||||
|
||||
mockRepo.EXPECT().Stage(mock.Anything, StageOptions{}).Return(mockStaged, nil)
|
||||
mockStaged.EXPECT().Push(mock.Anything).Return(nil)
|
||||
mockStaged.EXPECT().Remove(mock.Anything).Return(errors.New("remove failed"))
|
||||
|
||||
return &mockStagedRepo{
|
||||
MockStageableRepository: mockRepo,
|
||||
MockStagedRepository: mockStaged,
|
||||
}
|
||||
},
|
||||
operation: func(repo Repository, staged bool) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := tt.setupMocks(t)
|
||||
err := WrapWithStageAndPushIfPossible(context.Background(), repo, StageOptions{}, tt.operation)
|
||||
|
||||
if tt.expectedError != "" {
|
||||
require.EqualError(t, err, tt.expectedError)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func ValidateRepository(repo Repository) field.ErrorList {
|
||||
return list
|
||||
}
|
||||
|
||||
func fromFieldError(err *field.Error) *provisioning.TestResults {
|
||||
func FromFieldError(err *field.Error) *provisioning.TestResults {
|
||||
return &provisioning.TestResults{
|
||||
Code: http.StatusBadRequest,
|
||||
Success: false,
|
||||
|
||||
@@ -431,7 +431,7 @@ func TestFromFieldError(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := fromFieldError(tt.fieldError)
|
||||
result := FromFieldError(tt.fieldError)
|
||||
|
||||
require.NotNil(t, result)
|
||||
require.Equal(t, tt.expectedCode, result.Code)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package repository
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ func (r *ResourcesManager) WriteResourceFileFromObject(ctx context.Context, obj
|
||||
|
||||
err = r.repo.Write(ctx, fileName, options.Ref, body, commitMessage)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to write file: %w", err)
|
||||
return "", fmt.Errorf("failed to write file: %s, %w", fileName, err)
|
||||
}
|
||||
|
||||
return fileName, nil
|
||||
|
||||
@@ -11,9 +11,8 @@ import (
|
||||
provisioningapis "github.com/grafana/grafana/pkg/registry/apis/provisioning"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/git"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
gogit "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/go-git"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/nanogit"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/webhooks/pullrequest"
|
||||
@@ -180,42 +179,41 @@ func (e *WebhookExtra) AsRepository(ctx context.Context, r *provisioning.Reposit
|
||||
gvr.Resource,
|
||||
r.GetName(),
|
||||
)
|
||||
cloneFn := func(ctx context.Context, opts repository.CloneOptions) (repository.ClonedRepository, error) {
|
||||
return gogit.Clone(ctx, e.clonedir, r, opts, e.secrets)
|
||||
}
|
||||
|
||||
apiRepo, err := repository.NewGitHub(ctx, r, e.ghFactory, e.secrets, cloneFn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create github API repository: %w", err)
|
||||
}
|
||||
|
||||
logger := logging.FromContext(ctx).With("url", r.Spec.GitHub.URL, "branch", r.Spec.GitHub.Branch, "path", r.Spec.GitHub.Path)
|
||||
if !e.features.IsEnabledGlobally(featuremgmt.FlagNanoGit) {
|
||||
logger.Debug("Instantiating Github repository with go-git and Github API")
|
||||
return NewGithubWebhookRepository(apiRepo, webhookURL, e.secrets), nil
|
||||
}
|
||||
|
||||
logger.Info("Instantiating Github repository with nanogit")
|
||||
|
||||
logger.Info("Instantiating Github repository with webhooks")
|
||||
ghCfg := r.Spec.GitHub
|
||||
if ghCfg == nil {
|
||||
return nil, fmt.Errorf("github configuration is required for nano git")
|
||||
}
|
||||
|
||||
gitCfg := nanogit.RepositoryConfig{
|
||||
// Decrypt GitHub token if needed
|
||||
ghToken := ghCfg.Token
|
||||
if ghToken == "" && len(ghCfg.EncryptedToken) > 0 {
|
||||
decrypted, err := e.secrets.Decrypt(ctx, ghCfg.EncryptedToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt github token: %w", err)
|
||||
}
|
||||
ghToken = string(decrypted)
|
||||
}
|
||||
|
||||
gitCfg := git.RepositoryConfig{
|
||||
URL: ghCfg.URL,
|
||||
Branch: ghCfg.Branch,
|
||||
Path: ghCfg.Path,
|
||||
Token: ghCfg.Token,
|
||||
Token: ghToken,
|
||||
EncryptedToken: ghCfg.EncryptedToken,
|
||||
}
|
||||
|
||||
nanogitRepo, err := nanogit.NewGitRepository(ctx, e.secrets, r, gitCfg)
|
||||
gitRepo, err := git.NewGitRepository(ctx, r, gitCfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating nanogit repository: %w", err)
|
||||
return nil, fmt.Errorf("error creating git repository: %w", err)
|
||||
}
|
||||
|
||||
basicRepo := nanogit.NewGithubRepository(apiRepo, nanogitRepo)
|
||||
basicRepo, err := github.NewGitHub(ctx, r, gitRepo, e.ghFactory, ghToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating github repository: %w", err)
|
||||
}
|
||||
|
||||
return NewGithubWebhookRepository(basicRepo, webhookURL, e.secrets), nil
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ type WebhookRepository interface {
|
||||
}
|
||||
|
||||
type GithubWebhookRepository interface {
|
||||
repository.GithubRepository
|
||||
pgh.GithubRepository
|
||||
repository.Hooks
|
||||
|
||||
WebhookRepository
|
||||
}
|
||||
|
||||
type githubWebhookRepository struct {
|
||||
repository.GithubRepository
|
||||
pgh.GithubRepository
|
||||
config *provisioning.Repository
|
||||
owner string
|
||||
repo string
|
||||
@@ -42,7 +42,7 @@ type githubWebhookRepository struct {
|
||||
}
|
||||
|
||||
func NewGithubWebhookRepository(
|
||||
basic repository.GithubRepository,
|
||||
basic pgh.GithubRepository,
|
||||
webhookURL string,
|
||||
secrets secrets.Service,
|
||||
) GithubWebhookRepository {
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"testing"
|
||||
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
pgh "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -983,14 +983,14 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
func TestGitHubRepository_CommentPullRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(m *pgh.MockClient)
|
||||
setupMock func(m *github.MockClient)
|
||||
prNumber int
|
||||
comment string
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "successfully comment on pull request",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
m.On("CreatePullRequestComment", mock.Anything, "grafana", "grafana", 123, "Test comment").
|
||||
Return(nil)
|
||||
},
|
||||
@@ -1000,7 +1000,7 @@ func TestGitHubRepository_CommentPullRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error commenting on pull request",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
m.On("CreatePullRequestComment", mock.Anything, "grafana", "grafana", 456, "Error comment").
|
||||
Return(fmt.Errorf("failed to create comment"))
|
||||
},
|
||||
@@ -1013,7 +1013,7 @@ func TestGitHubRepository_CommentPullRequest(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Setup mock GitHub client
|
||||
mockGH := pgh.NewMockClient(t)
|
||||
mockGH := github.NewMockClient(t)
|
||||
tt.setupMock(mockGH)
|
||||
|
||||
// Create repository with mock
|
||||
@@ -1050,7 +1050,7 @@ func TestGitHubRepository_CommentPullRequest(t *testing.T) {
|
||||
func TestGitHubRepository_OnCreate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(m *pgh.MockClient)
|
||||
setupMock func(m *github.MockClient)
|
||||
config *provisioning.Repository
|
||||
webhookURL string
|
||||
expectedHook *provisioning.WebhookStatus
|
||||
@@ -1058,12 +1058,12 @@ func TestGitHubRepository_OnCreate(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "successfully create webhook",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(cfg pgh.WebhookConfig) bool {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(cfg github.WebhookConfig) bool {
|
||||
return cfg.URL == "https://example.com/webhook" &&
|
||||
cfg.ContentType == "json" &&
|
||||
cfg.Active == true
|
||||
})).Return(pgh.WebhookConfig{
|
||||
})).Return(github.WebhookConfig{
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
Secret: "test-secret",
|
||||
@@ -1086,7 +1086,7 @@ func TestGitHubRepository_OnCreate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no webhook URL",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// No webhook creation expected
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
@@ -1102,9 +1102,9 @@ func TestGitHubRepository_OnCreate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error creating webhook",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.Anything).
|
||||
Return(pgh.WebhookConfig{}, fmt.Errorf("failed to create webhook"))
|
||||
Return(github.WebhookConfig{}, fmt.Errorf("failed to create webhook"))
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
@@ -1122,7 +1122,7 @@ func TestGitHubRepository_OnCreate(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Setup mock GitHub client
|
||||
mockGH := pgh.NewMockClient(t)
|
||||
mockGH := github.NewMockClient(t)
|
||||
tt.setupMock(mockGH)
|
||||
|
||||
// Create repository with mock
|
||||
@@ -1166,7 +1166,7 @@ func TestGitHubRepository_OnCreate(t *testing.T) {
|
||||
func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(m *pgh.MockClient)
|
||||
setupMock func(m *github.MockClient)
|
||||
config *provisioning.Repository
|
||||
webhookURL string
|
||||
expectedHook *provisioning.WebhookStatus
|
||||
@@ -1174,17 +1174,17 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "successfully update webhook when webhook exists",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// Mock getting the existing webhook
|
||||
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
||||
Return(pgh.WebhookConfig{
|
||||
Return(github.WebhookConfig{
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
Events: []string{"push"},
|
||||
}, nil)
|
||||
|
||||
// Mock editing the webhook
|
||||
m.On("EditWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook pgh.WebhookConfig) bool {
|
||||
m.On("EditWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook github.WebhookConfig) bool {
|
||||
return hook.ID == 123 && hook.URL == "https://example.com/webhook-updated" &&
|
||||
slices.Equal(hook.Events, subscribedEvents)
|
||||
})).Return(nil)
|
||||
@@ -1212,18 +1212,18 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "create webhook when it doesn't exist",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// Mock webhook not found
|
||||
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
||||
Return(pgh.WebhookConfig{}, pgh.ErrResourceNotFound)
|
||||
Return(github.WebhookConfig{}, github.ErrResourceNotFound)
|
||||
|
||||
// Mock creating a new webhook
|
||||
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook pgh.WebhookConfig) bool {
|
||||
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook github.WebhookConfig) bool {
|
||||
return hook.URL == "https://example.com/webhook" &&
|
||||
hook.ContentType == "json" &&
|
||||
slices.Equal(hook.Events, subscribedEvents) &&
|
||||
hook.Active == true
|
||||
})).Return(pgh.WebhookConfig{
|
||||
})).Return(github.WebhookConfig{
|
||||
ID: 456,
|
||||
URL: "https://example.com/webhook",
|
||||
Events: subscribedEvents,
|
||||
@@ -1252,7 +1252,7 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no webhook URL provided",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// No mocks needed
|
||||
},
|
||||
config: &provisioning.Repository{},
|
||||
@@ -1262,9 +1262,9 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error getting webhook",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
||||
Return(pgh.WebhookConfig{}, fmt.Errorf("failed to get webhook"))
|
||||
Return(github.WebhookConfig{}, fmt.Errorf("failed to get webhook"))
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
@@ -1285,10 +1285,10 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error editing webhook",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// Mock getting the existing webhook
|
||||
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
||||
Return(pgh.WebhookConfig{
|
||||
Return(github.WebhookConfig{
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
Events: []string{"push"},
|
||||
@@ -1317,10 +1317,10 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "create webhook when webhook status is nil",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// Mock creating a new webhook
|
||||
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.Anything).
|
||||
Return(pgh.WebhookConfig{
|
||||
Return(github.WebhookConfig{
|
||||
ID: 456,
|
||||
URL: "https://example.com/webhook",
|
||||
Events: subscribedEvents,
|
||||
@@ -1348,10 +1348,10 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "create webhook when webhook ID is zero",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// Mock creating a new webhook
|
||||
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.Anything).
|
||||
Return(pgh.WebhookConfig{
|
||||
Return(github.WebhookConfig{
|
||||
ID: 789,
|
||||
URL: "https://example.com/webhook",
|
||||
Events: subscribedEvents,
|
||||
@@ -1382,10 +1382,10 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error when creating webhook fails",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// Mock webhook creation failure
|
||||
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.Anything).
|
||||
Return(pgh.WebhookConfig{}, fmt.Errorf("failed to create webhook"))
|
||||
Return(github.WebhookConfig{}, fmt.Errorf("failed to create webhook"))
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
@@ -1403,18 +1403,18 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "creates webhook when ErrResourceNotFound",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// Mock webhook not found
|
||||
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
||||
Return(pgh.WebhookConfig{}, pgh.ErrResourceNotFound)
|
||||
Return(github.WebhookConfig{}, github.ErrResourceNotFound)
|
||||
|
||||
// Mock creating a new webhook
|
||||
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook pgh.WebhookConfig) bool {
|
||||
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook github.WebhookConfig) bool {
|
||||
return hook.URL == "https://example.com/webhook" &&
|
||||
hook.ContentType == "json" &&
|
||||
slices.Equal(hook.Events, subscribedEvents) &&
|
||||
hook.Active == true
|
||||
})).Return(pgh.WebhookConfig{
|
||||
})).Return(github.WebhookConfig{
|
||||
ID: 456,
|
||||
URL: "https://example.com/webhook",
|
||||
Events: subscribedEvents,
|
||||
@@ -1443,18 +1443,18 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error on create when not found",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// Mock webhook not found
|
||||
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
||||
Return(pgh.WebhookConfig{}, pgh.ErrResourceNotFound)
|
||||
Return(github.WebhookConfig{}, github.ErrResourceNotFound)
|
||||
|
||||
// Mock error when creating a new webhook
|
||||
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook pgh.WebhookConfig) bool {
|
||||
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook github.WebhookConfig) bool {
|
||||
return hook.URL == "https://example.com/webhook" &&
|
||||
hook.ContentType == "json" &&
|
||||
slices.Equal(hook.Events, subscribedEvents) &&
|
||||
hook.Active == true
|
||||
})).Return(pgh.WebhookConfig{}, fmt.Errorf("failed to create webhook"))
|
||||
})).Return(github.WebhookConfig{}, fmt.Errorf("failed to create webhook"))
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
@@ -1475,10 +1475,10 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no update needed when URL and events match",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// Mock getting the existing webhook with matching URL and events
|
||||
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
||||
Return(pgh.WebhookConfig{
|
||||
Return(github.WebhookConfig{
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
Events: subscribedEvents,
|
||||
@@ -1514,7 +1514,7 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Setup mock GitHub client
|
||||
mockGH := pgh.NewMockClient(t)
|
||||
mockGH := github.NewMockClient(t)
|
||||
tt.setupMock(mockGH)
|
||||
|
||||
// Create repository with mock
|
||||
@@ -1563,14 +1563,14 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
func TestGitHubRepository_OnDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(m *pgh.MockClient)
|
||||
setupMock func(m *github.MockClient)
|
||||
config *provisioning.Repository
|
||||
webhookURL string
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "successfully delete webhook",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// Mock deleting the webhook
|
||||
m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
||||
Return(nil)
|
||||
@@ -1593,7 +1593,7 @@ func TestGitHubRepository_OnDelete(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no webhook URL provided",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// No mocks needed
|
||||
},
|
||||
config: &provisioning.Repository{},
|
||||
@@ -1602,7 +1602,7 @@ func TestGitHubRepository_OnDelete(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "webhook not found in status",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// No mocks needed
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
@@ -1620,7 +1620,7 @@ func TestGitHubRepository_OnDelete(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error deleting webhook",
|
||||
setupMock: func(m *pgh.MockClient) {
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// Mock webhook deletion failure
|
||||
m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
||||
Return(fmt.Errorf("failed to delete webhook"))
|
||||
@@ -1646,7 +1646,7 @@ func TestGitHubRepository_OnDelete(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Setup mock GitHub client
|
||||
mockGH := pgh.NewMockClient(t)
|
||||
mockGH := github.NewMockClient(t)
|
||||
tt.setupMock(mockGH)
|
||||
|
||||
// Create repository with mock
|
||||
|
||||
@@ -337,13 +337,6 @@ var (
|
||||
RequiresRestart: true,
|
||||
Owner: grafanaAppPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "nanoGit",
|
||||
Description: "Use experimental git library for provisioning",
|
||||
Stage: FeatureStageExperimental,
|
||||
RequiresRestart: true,
|
||||
Owner: grafanaAppPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "grafanaAPIServerEnsureKubectlAccess",
|
||||
Description: "Start an additional https handler and write kubectl options",
|
||||
|
||||
@@ -43,7 +43,6 @@ mlExpressions,experimental,@grafana/alerting-squad,false,false,false
|
||||
datasourceAPIServers,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
grafanaAPIServerWithExperimentalAPIs,experimental,@grafana/grafana-app-platform-squad,true,true,false
|
||||
provisioning,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
nanoGit,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
grafanaAPIServerEnsureKubectlAccess,experimental,@grafana/grafana-app-platform-squad,true,true,false
|
||||
featureToggleAdminPage,experimental,@grafana/grafana-operator-experience-squad,false,true,false
|
||||
awsAsyncQueryCaching,GA,@grafana/aws-datasources,false,false,false
|
||||
|
||||
|
@@ -183,10 +183,6 @@ const (
|
||||
// Next generation provisioning... and git
|
||||
FlagProvisioning = "provisioning"
|
||||
|
||||
// FlagNanoGit
|
||||
// Use experimental git library for provisioning
|
||||
FlagNanoGit = "nanoGit"
|
||||
|
||||
// FlagGrafanaAPIServerEnsureKubectlAccess
|
||||
// Start an additional https handler and write kubectl options
|
||||
FlagGrafanaAPIServerEnsureKubectlAccess = "grafanaAPIServerEnsureKubectlAccess"
|
||||
|
||||
@@ -2049,7 +2049,8 @@
|
||||
"metadata": {
|
||||
"name": "nanoGit",
|
||||
"resourceVersion": "1750434297879",
|
||||
"creationTimestamp": "2025-06-17T17:07:30Z"
|
||||
"creationTimestamp": "2025-06-17T17:07:30Z",
|
||||
"deletionTimestamp": "2025-07-09T11:53:17Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Use experimental git library for provisioning",
|
||||
|
||||
@@ -2,10 +2,7 @@ package provisioning
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
@@ -13,7 +10,6 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
gh "github.com/google/go-github/v70/github"
|
||||
ghmock "github.com/migueleliasweb/go-github-mock/src/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -224,20 +220,8 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper
|
||||
}
|
||||
helper := apis.NewK8sTestHelper(t, opts)
|
||||
|
||||
helper.GetEnv().GitHubFactory.Client = ghmock.NewMockedHTTPClient(
|
||||
ghmock.WithRequestMatchHandler(ghmock.GetUser, ghAlwaysWrite(t, &gh.User{})),
|
||||
ghmock.WithRequestMatchHandler(ghmock.GetReposHooksByOwnerByRepo, ghAlwaysWrite(t, []*gh.Hook{})),
|
||||
ghmock.WithRequestMatchHandler(ghmock.PostReposHooksByOwnerByRepo, ghAlwaysWrite(t, &gh.Hook{})),
|
||||
ghmock.WithRequestMatchHandler(ghmock.GetReposByOwnerByRepo, ghAlwaysWrite(t, &gh.Repository{})),
|
||||
ghmock.WithRequestMatchHandler(ghmock.GetReposBranchesByOwnerByRepoByBranch, ghAlwaysWrite(t, &gh.Branch{})),
|
||||
ghmock.WithRequestMatchHandler(ghmock.GetReposGitTreesByOwnerByRepoByTreeSha, ghAlwaysWrite(t, &gh.Tree{})),
|
||||
ghmock.WithRequestMatchHandler(
|
||||
ghmock.DeleteReposHooksByOwnerByRepoByHookId,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}),
|
||||
),
|
||||
)
|
||||
// FIXME: keeping this line here to keep the dependency around until we have tests which use this again.
|
||||
helper.GetEnv().GitHubFactory.Client = ghmock.NewMockedHTTPClient()
|
||||
|
||||
repositories := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: helper.Org1.Admin,
|
||||
@@ -310,33 +294,6 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper
|
||||
}
|
||||
}
|
||||
|
||||
func ghAlwaysWrite(t *testing.T, body any) http.HandlerFunc {
|
||||
marshalled := ghmock.MustMarshal(body)
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, err := w.Write(marshalled)
|
||||
require.NoError(t, err, "failed to write body in mock")
|
||||
})
|
||||
}
|
||||
|
||||
func ghHandleTree(t *testing.T, refs map[string][]*gh.TreeEntry) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
sha := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]
|
||||
require.NotEmpty(t, sha, "sha path parameter was missing?")
|
||||
|
||||
entries := refs[sha]
|
||||
require.NotNil(t, entries, "no entries for sha %s", sha)
|
||||
|
||||
tree := &gh.Tree{
|
||||
SHA: gh.Ptr(sha),
|
||||
Truncated: gh.Ptr(false),
|
||||
Entries: entries,
|
||||
}
|
||||
|
||||
_, err := w.Write(ghmock.MustMarshal(tree))
|
||||
require.NoError(t, err, "failed to write body in mock")
|
||||
})
|
||||
}
|
||||
|
||||
func mustNestedString(obj map[string]interface{}, fields ...string) string {
|
||||
v, _, err := unstructured.NestedString(obj, fields...)
|
||||
if err != nil {
|
||||
@@ -350,15 +307,6 @@ func asJSON(obj any) []byte {
|
||||
return jj
|
||||
}
|
||||
|
||||
func treeEntryDir(dirName string, sha string) *gh.TreeEntry {
|
||||
return &gh.TreeEntry{
|
||||
SHA: gh.Ptr(sha),
|
||||
Path: gh.Ptr(dirName),
|
||||
Type: gh.Ptr("tree"),
|
||||
Mode: gh.Ptr("040000"),
|
||||
}
|
||||
}
|
||||
|
||||
func unstructuredToRepository(t *testing.T, obj *unstructured.Unstructured) *provisioning.Repository {
|
||||
bytes, err := obj.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
@@ -369,33 +317,3 @@ func unstructuredToRepository(t *testing.T, obj *unstructured.Unstructured) *pro
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func treeEntry(fpath string, content []byte) *gh.TreeEntry {
|
||||
sha := sha256.Sum256(content)
|
||||
|
||||
return &gh.TreeEntry{
|
||||
SHA: gh.Ptr(hex.EncodeToString(sha[:])),
|
||||
Path: gh.Ptr(fpath),
|
||||
Size: gh.Ptr(len(content)),
|
||||
Type: gh.Ptr("blob"),
|
||||
Mode: gh.Ptr("100644"),
|
||||
Content: gh.Ptr(string(content)),
|
||||
}
|
||||
}
|
||||
|
||||
func repoContent(fpath string, content []byte) *gh.RepositoryContent {
|
||||
sha := sha256.Sum256(content)
|
||||
typ := "blob"
|
||||
if strings.HasSuffix(fpath, "/") {
|
||||
typ = "tree"
|
||||
}
|
||||
|
||||
return &gh.RepositoryContent{
|
||||
SHA: gh.Ptr(hex.EncodeToString(sha[:])),
|
||||
Name: gh.Ptr(path.Base(fpath)),
|
||||
Path: &fpath,
|
||||
Size: gh.Ptr(len(content)),
|
||||
Type: &typ,
|
||||
Content: gh.Ptr(string(content)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,10 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gh "github.com/google/go-github/v70/github"
|
||||
ghmock "github.com/migueleliasweb/go-github-mock/src/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -174,7 +171,7 @@ func TestIntegrationProvisioning_FailInvalidSchema(t *testing.T) {
|
||||
}, time.Second*10, time.Millisecond*10, "Expected to be able to start a sync job")
|
||||
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
//helper.TriggerJobProcessing(t)
|
||||
// helper.TriggerJobProcessing(t)
|
||||
result, err := helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{},
|
||||
"jobs", string(jobObj.GetUID()))
|
||||
|
||||
@@ -212,50 +209,25 @@ func TestIntegrationProvisioning_CreatingGitHubRepository(t *testing.T) {
|
||||
helper := runGrafana(t)
|
||||
ctx := context.Background()
|
||||
|
||||
helper.GetEnv().GitHubFactory.Client = ghmock.NewMockedHTTPClient(
|
||||
ghmock.WithRequestMatchHandler(ghmock.GetUser, ghAlwaysWrite(t, &gh.User{Name: gh.Ptr("github-user")})),
|
||||
ghmock.WithRequestMatchHandler(ghmock.GetReposHooksByOwnerByRepo, ghAlwaysWrite(t, []*gh.Hook{})),
|
||||
ghmock.WithRequestMatchHandler(ghmock.PostReposHooksByOwnerByRepo, ghAlwaysWrite(t, &gh.Hook{ID: gh.Ptr(int64(123))})),
|
||||
ghmock.WithRequestMatchHandler(ghmock.GetReposByOwnerByRepo, ghAlwaysWrite(t, &gh.Repository{ID: gh.Ptr(int64(234))})),
|
||||
ghmock.WithRequestMatchHandler(
|
||||
ghmock.GetReposBranchesByOwnerByRepoByBranch,
|
||||
ghAlwaysWrite(t, &gh.Branch{
|
||||
Name: gh.Ptr("main"),
|
||||
Commit: &gh.RepositoryCommit{SHA: gh.Ptr("deadbeef")},
|
||||
}),
|
||||
),
|
||||
ghmock.WithRequestMatchHandler(ghmock.GetReposGitTreesByOwnerByRepoByTreeSha,
|
||||
ghHandleTree(t, map[string][]*gh.TreeEntry{
|
||||
"deadbeef": {
|
||||
treeEntryDir("grafana", "subtree"),
|
||||
},
|
||||
"subtree": {
|
||||
treeEntry("dashboard.json", helper.LoadFile("testdata/all-panels.json")),
|
||||
treeEntryDir("subdir", "subtree2"),
|
||||
treeEntry("subdir/dashboard2.yaml", helper.LoadFile("testdata/text-options.json")),
|
||||
},
|
||||
})),
|
||||
ghmock.WithRequestMatchHandler(
|
||||
ghmock.GetReposContentsByOwnerByRepoByPath,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
pathRegex := regexp.MustCompile(`/repos/[^/]+/[^/]+/contents/(.*)`)
|
||||
matches := pathRegex.FindStringSubmatch(r.URL.Path)
|
||||
require.NotNil(t, matches, "no match for contents?")
|
||||
path := matches[1]
|
||||
// FIXME: instead of using an existing GitHub repository, we should create a new one for the tests and a branch
|
||||
// This was the previous structure
|
||||
// ghmock.WithRequestMatchHandler(ghmock.GetReposGitTreesByOwnerByRepoByTreeSha,
|
||||
// ghHandleTree(t, map[string][]*gh.TreeEntry{
|
||||
// "deadbeef": {
|
||||
// treeEntryDir("grafana", "subtree"),
|
||||
// },
|
||||
// "subtree": {
|
||||
// treeEntry("dashboard.json", helper.LoadFile("testdata/all-panels.json")),
|
||||
// treeEntryDir("subdir", "subtree2"),
|
||||
// treeEntry("subdir/dashboard2.yaml", helper.LoadFile("testdata/text-options.json")),
|
||||
// },
|
||||
// })),
|
||||
|
||||
var err error
|
||||
switch path {
|
||||
case "grafana/dashboard.json":
|
||||
_, err = w.Write(ghmock.MustMarshal(repoContent(path, helper.LoadFile("testdata/all-panels.json"))))
|
||||
case "grafana/subdir/dashboard2.yaml":
|
||||
_, err = w.Write(ghmock.MustMarshal(repoContent(path, helper.LoadFile("testdata/text-options.json"))))
|
||||
default:
|
||||
t.Fatalf("got unexpected path: %s", path)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
),
|
||||
)
|
||||
// FIXME: uncomment these to implement webhook integration tests.
|
||||
// helper.GetEnv().GitHubFactory.Client = ghmock.NewMockedHTTPClient(
|
||||
// ghmock.WithRequestMatchHandler(ghmock.GetReposHooksByOwnerByRepo, ghAlwaysWrite(t, []*gh.Hook{})),
|
||||
// ghmock.WithRequestMatchHandler(ghmock.PostReposHooksByOwnerByRepo, ghAlwaysWrite(t, &gh.Hook{ID: gh.Ptr(int64(123))})),
|
||||
// )
|
||||
|
||||
const repo = "github-create-test"
|
||||
_, err := helper.Repositories.Resource.Create(ctx,
|
||||
@@ -280,8 +252,10 @@ func TestIntegrationProvisioning_CreatingGitHubRepository(t *testing.T) {
|
||||
for _, v := range found.Items {
|
||||
names = append(names, v.GetName())
|
||||
}
|
||||
assert.Contains(t, names, "n1jR8vnnz", "should contain dashboard.json's contents")
|
||||
assert.Contains(t, names, "WZ7AhQiVz", "should contain dashboard2.yaml's contents")
|
||||
require.Len(t, names, 3, "should have three dashboards")
|
||||
assert.Contains(t, names, "adg5vbj", "should contain dashboard.json's contents")
|
||||
assert.Contains(t, names, "admfz74", "should contain dashboard2.yaml's contents")
|
||||
assert.Contains(t, names, "adn5mxb", "should contain dashboard2.yaml's contents")
|
||||
|
||||
err = helper.Repositories.Resource.Delete(ctx, repo, metav1.DeleteOptions{})
|
||||
require.NoError(t, err, "should delete values")
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
"description": "{{ or .Description .Name "Load grafana dashboard from fake repository" }}",
|
||||
"type": "github",
|
||||
"github": {
|
||||
"url": "{{ or .URL "https://github.com/grafana/git-ui-sync-demo" }}",
|
||||
"branch": "{{ or .Branch "dummy" }}",
|
||||
"url": "{{ or .URL "https://github.com/grafana/grafana-git-sync-demo" }}",
|
||||
"branch": "{{ or .Branch "integration-test" }}",
|
||||
"generateDashboardPreviews": {{ if .GenerateDashboardPreviews }} true {{ else }} false {{ end }},
|
||||
"token": "{{ or .Token "github_pat_dummy" }}",
|
||||
"token": "{{ or .Token "" }}",
|
||||
"path": "{{ or .Path "grafana/" }}"
|
||||
},
|
||||
"sync": {
|
||||
|
||||
Reference in New Issue
Block a user