App Platform Provisioning: Add experimental nanogit mode for Git Sync (#106763)
* Add nanogit package * Add nanoGit feature flag * Put logger into nanogit context * Commit go mod and go sum updates * Add more stuff around logging * Nanogit also in extra one * Add owner to dependency
This commit is contained in:
@@ -34,7 +34,7 @@ require (
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
|
||||
|
||||
@@ -78,8 +78,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
|
||||
@@ -29,7 +29,7 @@ require (
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafana/grafana-app-sdk/logging v0.38.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
||||
|
||||
@@ -43,8 +43,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grafana/grafana-app-sdk v0.39.0 h1:WC2E9BKXWDX/e2bajdAFjQEyyWf9BFp7Yzbjm6T1T1w=
|
||||
|
||||
@@ -96,6 +96,7 @@ require (
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.277.0 // @grafana/plugins-platform-backend
|
||||
github.com/grafana/loki/v3 v3.2.1 // @grafana/observability-logs
|
||||
github.com/grafana/nanogit v0.0.0-20250617163817-5673133b88c5 // @grafana-app-platform-squad0
|
||||
github.com/grafana/otel-profiling-go v0.5.1 // @grafana/grafana-backend-group
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // @grafana/observability-traces-and-profiling
|
||||
github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae // @grafana/observability-traces-and-profiling
|
||||
|
||||
@@ -1506,8 +1506,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM=
|
||||
github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||
@@ -1637,6 +1637,8 @@ github.com/grafana/loki/pkg/push v0.0.0-20231124142027-e52380921608 h1:ZYk42718k
|
||||
github.com/grafana/loki/pkg/push v0.0.0-20231124142027-e52380921608/go.mod h1:f3JSoxBTPXX5ec4FxxeC19nTBSxoTz+cBgS3cYLMcr0=
|
||||
github.com/grafana/loki/v3 v3.2.1 h1:VB7u+KHfvL5aHAxgoVBvz5wVhsdGuqKC7uuOFOOe7jw=
|
||||
github.com/grafana/loki/v3 v3.2.1/go.mod h1:WvdLl6wOS+yahaeQY+xhD2m2XzkHDfKr5FZaX7D/X2Y=
|
||||
github.com/grafana/nanogit v0.0.0-20250617163817-5673133b88c5 h1:VaJW9la/ey2DhlJxZRKuDE0kyMbkpILuXZxtKf827UY=
|
||||
github.com/grafana/nanogit v0.0.0-20250617163817-5673133b88c5/go.mod h1:tN93IZUaAmnSWgL0IgnKdLv6DNeIhTJGvl1wvQMrWco=
|
||||
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
|
||||
github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
|
||||
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36 h1:AjZ58JRw1ZieFH/SdsddF5BXtsDKt5kSrKNPWrzYz3Y=
|
||||
|
||||
+34
-34
@@ -596,6 +596,8 @@ github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d h1:j6oB/WPC
|
||||
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2Aq4ZODqTDkeSqQBy+fzpZPamacO1Srp8zq7jf2Sc=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/azure-amqp-common-go/v3 v3.2.3 h1:uDF62mbd9bypXWi19V1bN5NZEO84JqgmI5G73ibAmrk=
|
||||
github.com/Azure/azure-amqp-common-go/v3 v3.2.3/go.mod h1:7rPmbSfszeovxGfc5fSAXE4ehlXQZHpMja2OtxC2Tas=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4=
|
||||
@@ -879,12 +881,12 @@ github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj
|
||||
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk=
|
||||
github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
|
||||
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=
|
||||
@@ -919,6 +921,8 @@ github.com/containerd/go-runc v1.0.0 h1:oU+lLv1ULm5taqgV/CJivypVODI4SUz1znWjv3nN
|
||||
github.com/containerd/imgcrypt v1.1.4 h1:iKTstFebwy3Ak5UF0RHSeuCTahC5OIrPJa6vjMAM81s=
|
||||
github.com/containerd/imgcrypt v1.1.4/go.mod h1:LorQnPtzL/T0IyCeftcsMEO7AqxUDbdO8j/tSUpgxvo=
|
||||
github.com/containerd/nri v0.1.0 h1:6QioHRlThlKh2RkRTR4kIT3PKAcrLo3gIWnjkM4dQmQ=
|
||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/containerd/ttrpc v1.1.0 h1:GbtyLRxb0gOLR0TYQWt3O6B0NvT8tMdorEHqIQo/lWI=
|
||||
github.com/containerd/typeurl v1.0.2 h1:Chlt8zIieDbzQFzXzAeBEF92KhExuE4p9p92/QmY7aY=
|
||||
github.com/containerd/zfs v1.0.0 h1:cXLJbx+4Jj7rNsTiqVfm6i+RNLx6FFA2fMmDlEf+Wm8=
|
||||
@@ -949,6 +953,8 @@ github.com/couchbase/ghistogram v0.1.0 h1:b95QcQTCzjTUocDXp/uMgSNQi8oj1tGwnJ4bOD
|
||||
github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k=
|
||||
github.com/couchbase/moss v0.2.0 h1:VCYrMzFwEryyhRSeI+/b3tRBSeTpi/8gn5Kf6dxqn+o=
|
||||
github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
@@ -1283,12 +1289,8 @@ github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2 h1:qhugDMdQ4
|
||||
github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2/go.mod h1:w/aiO1POVIeXUQyl0VQSZjl5OAGDTL5aX+4v0RA1tcw=
|
||||
github.com/grafana/cog v0.0.23 h1:/0CCJ24Z8XXM2DnboSd2FzoIswUroqIZzVr8oJWmMQs=
|
||||
github.com/grafana/cog v0.0.23/go.mod h1:jrS9indvWuDs60RHEZpLaAkmZdgyoLKMOEUT0jiB1t0=
|
||||
github.com/grafana/cog v0.0.35 h1:ZrGca+X17lczqhuXVCxHbLyGszP8VHGh1eAQLxbplkY=
|
||||
github.com/grafana/cog v0.0.35/go.mod h1:UDstzYqMdgIROmbfkHL8fB9XWQO2lnf5z+4W/eJo4Dc=
|
||||
github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak=
|
||||
github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90=
|
||||
github.com/grafana/grafana-app-sdk v0.39.0 h1:WC2E9BKXWDX/e2bajdAFjQEyyWf9BFp7Yzbjm6T1T1w=
|
||||
github.com/grafana/grafana-app-sdk v0.39.0/go.mod h1:xRyBQOttgWTc3tGe9pI0upnpEPVhzALf7Mh/61O4zyY=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.38.0/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.39.0 h1:3GgN5+dUZYqq74Q+GT9/ET+yo+V54zWQk/Q2/JsJQB4=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.39.0/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk=
|
||||
@@ -1301,6 +1303,8 @@ github.com/grafana/grafana/apps/advisor v0.0.0-20250220154326-6e5de80ef295/go.mo
|
||||
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250121113133-e747350fee2d/go.mod h1:AvleS6icyPmcBjihtx5jYEvdzLmHGBp66NuE0AMR57A=
|
||||
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250416173722-ec17e0e4ce03/go.mod h1:oemrhKvFxxc5m32xKHPxInEHAObH0/hPPyHUiBUZ1Cc=
|
||||
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250506052906-7a2fc797fb4a/go.mod h1:VkX53kBiqIMHBoGgeEDJnzm5Nwcmv/726tuZuT5SvJY=
|
||||
github.com/grafana/grafana/apps/dashboard v0.0.0-20250616135341-59c2f154336b/go.mod h1:OIlvNnUufYDhBXa4xK4CyzPI2C69ZJkHy5+aFDyPtXw=
|
||||
github.com/grafana/grafana/apps/dashboard v0.0.0-20250616145019-8d27f12428cb/go.mod h1:OIlvNnUufYDhBXa4xK4CyzPI2C69ZJkHy5+aFDyPtXw=
|
||||
github.com/grafana/grafana/apps/investigation v0.0.0-20250121113133-e747350fee2d/go.mod h1:HQprw3MmiYj5OUV9CZnkwA1FKDZBmYACuAB3oDvUOmI=
|
||||
github.com/grafana/grafana/apps/playlist v0.0.0-20250121113133-e747350fee2d/go.mod h1:DjJe5osrW/BKrzN9hAAOSElNWutj1bcriExa7iDP7kA=
|
||||
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250121113133-e747350fee2d/go.mod h1:1sq0guad+G4SUTlBgx7SXfhnzy7D86K/LcVOtiQCiMA=
|
||||
@@ -1311,6 +1315,8 @@ github.com/grafana/grafana/pkg/build v0.0.0-20250403075254-4918d8720c61/go.mod h
|
||||
github.com/grafana/grafana/pkg/semconv v0.0.0-20250121113133-e747350fee2d/go.mod h1:tfLnBpPYgwrBMRz4EXqPCZJyCjEG4Ev37FSlXnocJ2c=
|
||||
github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20250121113133-e747350fee2d/go.mod h1:CXpwZ3Mkw6xVlGKc0SqUxqXCP3Uv182q6qAQnLaLxRg=
|
||||
github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:xrKQcxQxz+IUF90ybtfENFeEXtlj9nAsX/3Fw0KEIeQ=
|
||||
github.com/grafana/nanogit v0.0.0-20250616082354-5e94194d02ed h1:59JF1WhHLT+lNX89Tm1OzOEySMVMASAhaPbsRjtp8Kc=
|
||||
github.com/grafana/nanogit v0.0.0-20250616082354-5e94194d02ed/go.mod h1:OIAAKNgG5fpuJQRNO1lUSj9nc18Xl3O7M8fjIlBO1cI=
|
||||
github.com/grafana/prometheus-alertmanager v0.25.1-0.20240930132144-b5e64e81e8d3 h1:6D2gGAwyQBElSrp3E+9lSr7k8gLuP3Aiy20rweLWeBw=
|
||||
github.com/grafana/prometheus-alertmanager v0.25.1-0.20240930132144-b5e64e81e8d3/go.mod h1:YeND+6FDA7OuFgDzYODN8kfPhXLCehcpxe4T9mdnpCY=
|
||||
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975 h1:4/BZkGObFWZf4cLbE2Vqg/1VTz67Q0AJ7LHspWLKJoQ=
|
||||
@@ -1508,6 +1514,8 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
|
||||
github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
|
||||
github.com/marstr/guid v1.1.0 h1:/M4H/1G4avsieL6BbUwCOBzulmoeKVP5ux/3mQNnbyI=
|
||||
@@ -1525,6 +1533,8 @@ github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+nt
|
||||
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2 h1:yVCLo4+ACVroOEr4iFU1iH46Ldlzz2rTuu18Ra7M8sU=
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2/go.mod h1:VzB2VoMh1Y32/QqDfg9ZJYHj99oM4LiGtqPZydTiQSQ=
|
||||
github.com/mfridman/xflag v0.1.0 h1:TWZrZwG1QklFX5S4j1vxfF1sZbZeZSGofMwPMLAF29M=
|
||||
github.com/mfridman/xflag v0.1.0/go.mod h1:/483ywM5ZO5SuMVjrIGquYNE5CzLrj5Ux/LxWWnjRaE=
|
||||
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0=
|
||||
@@ -1554,15 +1564,22 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/51gbeLHfKGNfgEQQIWrlbdaOsidbQ=
|
||||
github.com/mithrandie/readline-csvq v1.3.0 h1:VTJEOGouJ8j27jJCD4kBBbNTxM0OdBvE1aY1tMhlqE8=
|
||||
github.com/mithrandie/readline-csvq v1.3.0/go.mod h1:FKyYqDgf/G4SNov7SMFXRWO6LQLXIOeTog/NB97FZl0=
|
||||
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI=
|
||||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/signal v0.6.0 h1:aDpY94H8VlhTGa9sNYUFCFsMZIUh5wm0B6XkIoJj/iY=
|
||||
github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg=
|
||||
github.com/moby/sys/symlink v0.2.0 h1:tk1rOM+Ljp0nFmfOIBtlV3rTDlWOwFRhjEeAhZB0nZc=
|
||||
github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs=
|
||||
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
|
||||
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
|
||||
@@ -1612,6 +1629,8 @@ github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
|
||||
github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM=
|
||||
github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
@@ -1619,6 +1638,8 @@ github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
|
||||
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/open-feature/go-sdk v1.11.0/go.mod h1:+rkJhLBtYsJ5PZNddAgFILhRAAxwrJ32aU7UEUm4zQI=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/exporter/kafkaexporter v0.102.0 h1:R70PpK14trQfL/Vj5oAiGRqX09s2gOWuf6t1Ae5fevQ=
|
||||
@@ -1702,6 +1723,8 @@ github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceive
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.124.1/go.mod h1:CuCZVPz+yn88b5vhZPAlxaMrVuhAVexUV6f8b07lpUc=
|
||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/opencontainers/runc v1.1.2 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw=
|
||||
github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc=
|
||||
github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
|
||||
@@ -1829,6 +1852,8 @@ github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtr
|
||||
github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
@@ -1879,6 +1904,8 @@ github.com/tdewolff/minify/v2 v2.12.8 h1:Q2BqOTmlMjoutkuD/OPCnJUpIqrzT3nRPkw+q+K
|
||||
github.com/tdewolff/minify/v2 v2.12.8/go.mod h1:YRgk7CC21LZnbuke2fmYnCTq+zhCgpb0yJACOTUNJ1E=
|
||||
github.com/tdewolff/parse/v2 v2.6.7 h1:WrFllrqmzAcrKHzoYgMupqgUBIfBVOb0yscFzDf8bBg=
|
||||
github.com/tdewolff/parse/v2 v2.6.7/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM=
|
||||
github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo=
|
||||
github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
@@ -2253,8 +2280,6 @@ go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIj
|
||||
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
||||
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel/bridge/opencensus v1.27.0 h1:ao9aGGHd+G4YfjBpGs6vbkvt5hoC67STlJA9fCnOAcs=
|
||||
go.opentelemetry.io/otel/bridge/opencensus v1.27.0/go.mod h1:uRvWtAAXzyVOST0WMPX5JHGBaAvBws+2F8PcC5gMnTk=
|
||||
go.opentelemetry.io/otel/bridge/opencensus v1.35.0 h1:4nJfffRbozhqnuukfRkiahA94mnpryCLJLiduMIDJKI=
|
||||
@@ -2270,14 +2295,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.50.0 h1:2Ewsda6hejmbhGFyUvWZjUThC98Cf8Zy6g0zkIimOng=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.50.0/go.mod h1:pMm5PkUo5YwbLiuEf7t2xg4wbP0/eSJrMxIMxKosynY=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk=
|
||||
@@ -2297,16 +2316,12 @@ go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUis
|
||||
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
||||
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
|
||||
go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok=
|
||||
go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg=
|
||||
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
@@ -2317,8 +2332,6 @@ go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVf
|
||||
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
|
||||
@@ -2359,8 +2372,6 @@ golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
@@ -2388,8 +2399,6 @@ golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
@@ -2411,8 +2420,6 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||
@@ -2422,8 +2429,6 @@ golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -2457,8 +2462,6 @@ golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
@@ -2483,8 +2486,6 @@ golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
|
||||
@@ -2598,8 +2599,6 @@ google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7Qf
|
||||
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
|
||||
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE=
|
||||
google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20 h1:MLBCGN1O7GzIx+cBiwfYPwtmZ41U3Mn/cotLJciaArI=
|
||||
google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0=
|
||||
@@ -2633,6 +2632,7 @@ gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
honnef.co/go/tools v0.3.2 h1:ytYb4rOqyp1TSa2EPvNVwtPQJctSELKaMyLfqNP4+34=
|
||||
honnef.co/go/tools v0.3.2/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
|
||||
@@ -218,6 +218,10 @@ export interface FeatureToggles {
|
||||
*/
|
||||
provisioning?: boolean;
|
||||
/**
|
||||
* Use experimental git library for provisioning
|
||||
*/
|
||||
nanoGit?: boolean;
|
||||
/**
|
||||
* Start an additional https handler and write kubectl options
|
||||
*/
|
||||
grafanaAPIServerEnsureKubectlAccess?: boolean;
|
||||
|
||||
@@ -58,7 +58,7 @@ require (
|
||||
github.com/google/flatbuffers v25.2.10+incompatible // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grafana/otel-profiling-go v0.5.1 // indirect
|
||||
|
||||
@@ -121,8 +121,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
|
||||
@@ -34,7 +34,7 @@ require (
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
|
||||
|
||||
@@ -70,8 +70,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
|
||||
@@ -41,7 +41,7 @@ require (
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250515162837-2f4a8263eabb // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
|
||||
@@ -72,8 +72,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
|
||||
@@ -47,6 +47,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
"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/resources/signature"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||
@@ -167,6 +168,13 @@ 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{
|
||||
PermittedPrefixes: cfg.PermittedProvisioningPaths,
|
||||
HomePath: safepath.Clean(cfg.HomePath),
|
||||
@@ -1122,7 +1130,39 @@ func (b *APIBuilder) AsRepository(ctx context.Context, r *provisioning.Repositor
|
||||
cloneFn := func(ctx context.Context, opts repository.CloneOptions) (repository.ClonedRepository, error) {
|
||||
return gogit.Clone(ctx, b.clonedir, r, opts, b.secrets)
|
||||
}
|
||||
return repository.NewGitHub(ctx, r, b.ghFactory, b.secrets, cloneFn)
|
||||
|
||||
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")
|
||||
|
||||
ghCfg := r.Spec.GitHub
|
||||
if ghCfg == nil {
|
||||
return nil, fmt.Errorf("github configuration is required for nano git")
|
||||
}
|
||||
|
||||
gitCfg := nanogit.RepositoryConfig{
|
||||
URL: ghCfg.URL,
|
||||
Branch: ghCfg.Branch,
|
||||
Path: ghCfg.Path,
|
||||
Token: ghCfg.Token,
|
||||
EncryptedToken: ghCfg.EncryptedToken,
|
||||
}
|
||||
|
||||
nanogitRepo, err := nanogit.NewGitRepository(ctx, b.secrets, r, gitCfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating nanogit repository: %w", err)
|
||||
}
|
||||
|
||||
return nanogit.NewGithubRepository(apiRepo, nanogitRepo), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown repository type (%s)", r.Spec.Type)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package 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
|
||||
URL() string
|
||||
Branch() string
|
||||
}
|
||||
@@ -0,0 +1,834 @@
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
// MockGitRepository is an autogenerated mock type for the GitRepository type
|
||||
type MockGitRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockGitRepository_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockGitRepository) EXPECT() *MockGitRepository_Expecter {
|
||||
return &MockGitRepository_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Branch provides a mock function with no fields
|
||||
func (_m *MockGitRepository) Branch() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Branch")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGitRepository_Branch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Branch'
|
||||
type MockGitRepository_Branch_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Branch is a helper method to define mock.On call
|
||||
func (_e *MockGitRepository_Expecter) Branch() *MockGitRepository_Branch_Call {
|
||||
return &MockGitRepository_Branch_Call{Call: _e.mock.On("Branch")}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Branch_Call) Run(run func()) *MockGitRepository_Branch_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Branch_Call) Return(_a0 string) *MockGitRepository_Branch_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Branch_Call) RunAndReturn(run func() string) *MockGitRepository_Branch_Call {
|
||||
_c.Call.Return(run)
|
||||
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) {
|
||||
ret := _m.Called(ctx, base, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CompareFiles")
|
||||
}
|
||||
|
||||
var r0 []VersionedFileChange
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]VersionedFileChange, error)); ok {
|
||||
return rf(ctx, base, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []VersionedFileChange); ok {
|
||||
r0 = rf(ctx, base, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]VersionedFileChange)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, base, ref)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGitRepository_CompareFiles_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CompareFiles'
|
||||
type MockGitRepository_CompareFiles_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CompareFiles is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - base string
|
||||
// - ref string
|
||||
func (_e *MockGitRepository_Expecter) CompareFiles(ctx interface{}, base interface{}, ref interface{}) *MockGitRepository_CompareFiles_Call {
|
||||
return &MockGitRepository_CompareFiles_Call{Call: _e.mock.On("CompareFiles", ctx, base, ref)}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_CompareFiles_Call) Run(run func(ctx context.Context, base string, ref string)) *MockGitRepository_CompareFiles_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_CompareFiles_Call) Return(_a0 []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 {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Config provides a mock function with no fields
|
||||
func (_m *MockGitRepository) Config() *v0alpha1.Repository {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Config")
|
||||
}
|
||||
|
||||
var r0 *v0alpha1.Repository
|
||||
if rf, ok := ret.Get(0).(func() *v0alpha1.Repository); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*v0alpha1.Repository)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGitRepository_Config_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Config'
|
||||
type MockGitRepository_Config_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Config is a helper method to define mock.On call
|
||||
func (_e *MockGitRepository_Expecter) Config() *MockGitRepository_Config_Call {
|
||||
return &MockGitRepository_Config_Call{Call: _e.mock.On("Config")}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Config_Call) Run(run func()) *MockGitRepository_Config_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Config_Call) Return(_a0 *v0alpha1.Repository) *MockGitRepository_Config_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Config_Call) RunAndReturn(run func() *v0alpha1.Repository) *MockGitRepository_Config_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, path, ref, data, message
|
||||
func (_m *MockGitRepository) 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 {
|
||||
panic("no return value specified for Create")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok {
|
||||
r0 = rf(ctx, path, ref, data, message)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGitRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
|
||||
type MockGitRepository_Create_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Create is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
// - data []byte
|
||||
// - message string
|
||||
func (_e *MockGitRepository_Expecter) Create(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockGitRepository_Create_Call {
|
||||
return &MockGitRepository_Create_Call{Call: _e.mock.On("Create", ctx, path, ref, data, message)}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Create_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockGitRepository_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 *MockGitRepository_Create_Call) Return(_a0 error) *MockGitRepository_Create_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Create_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockGitRepository_Create_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, path, ref, message
|
||||
func (_m *MockGitRepository) Delete(ctx context.Context, path string, ref string, message string) error {
|
||||
ret := _m.Called(ctx, path, ref, message)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Delete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
|
||||
r0 = rf(ctx, path, ref, message)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGitRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
|
||||
type MockGitRepository_Delete_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Delete is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
// - message string
|
||||
func (_e *MockGitRepository_Expecter) Delete(ctx interface{}, path interface{}, ref interface{}, message interface{}) *MockGitRepository_Delete_Call {
|
||||
return &MockGitRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, path, ref, message)}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Delete_Call) Run(run func(ctx context.Context, path string, ref string, message string)) *MockGitRepository_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 *MockGitRepository_Delete_Call) Return(_a0 error) *MockGitRepository_Delete_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Delete_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockGitRepository_Delete_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// History provides a mock function with given fields: ctx, path, ref
|
||||
func (_m *MockGitRepository) History(ctx context.Context, path string, ref string) ([]v0alpha1.HistoryItem, error) {
|
||||
ret := _m.Called(ctx, path, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for History")
|
||||
}
|
||||
|
||||
var r0 []v0alpha1.HistoryItem
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]v0alpha1.HistoryItem, error)); ok {
|
||||
return rf(ctx, path, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []v0alpha1.HistoryItem); ok {
|
||||
r0 = rf(ctx, path, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]v0alpha1.HistoryItem)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, path, ref)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGitRepository_History_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'History'
|
||||
type MockGitRepository_History_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// History is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
func (_e *MockGitRepository_Expecter) History(ctx interface{}, path interface{}, ref interface{}) *MockGitRepository_History_Call {
|
||||
return &MockGitRepository_History_Call{Call: _e.mock.On("History", ctx, path, ref)}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_History_Call) Run(run func(ctx context.Context, path string, ref string)) *MockGitRepository_History_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_History_Call) Return(_a0 []v0alpha1.HistoryItem, _a1 error) *MockGitRepository_History_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_History_Call) RunAndReturn(run func(context.Context, string, string) ([]v0alpha1.HistoryItem, error)) *MockGitRepository_History_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// LatestRef provides a mock function with given fields: ctx
|
||||
func (_m *MockGitRepository) LatestRef(ctx context.Context) (string, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for LatestRef")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok {
|
||||
return rf(ctx)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context) string); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGitRepository_LatestRef_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LatestRef'
|
||||
type MockGitRepository_LatestRef_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// LatestRef is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockGitRepository_Expecter) LatestRef(ctx interface{}) *MockGitRepository_LatestRef_Call {
|
||||
return &MockGitRepository_LatestRef_Call{Call: _e.mock.On("LatestRef", ctx)}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_LatestRef_Call) Run(run func(ctx context.Context)) *MockGitRepository_LatestRef_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_LatestRef_Call) Return(_a0 string, _a1 error) *MockGitRepository_LatestRef_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_LatestRef_Call) RunAndReturn(run func(context.Context) (string, error)) *MockGitRepository_LatestRef_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Read provides a mock function with given fields: ctx, path, ref
|
||||
func (_m *MockGitRepository) Read(ctx context.Context, path string, ref string) (*FileInfo, error) {
|
||||
ret := _m.Called(ctx, path, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Read")
|
||||
}
|
||||
|
||||
var r0 *FileInfo
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*FileInfo, error)); ok {
|
||||
return rf(ctx, path, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) *FileInfo); ok {
|
||||
r0 = rf(ctx, path, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*FileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, path, ref)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGitRepository_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read'
|
||||
type MockGitRepository_Read_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Read is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
func (_e *MockGitRepository_Expecter) Read(ctx interface{}, path interface{}, ref interface{}) *MockGitRepository_Read_Call {
|
||||
return &MockGitRepository_Read_Call{Call: _e.mock.On("Read", ctx, path, ref)}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Read_Call) Run(run func(ctx context.Context, path string, ref string)) *MockGitRepository_Read_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Read_Call) Return(_a0 *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 {
|
||||
_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) {
|
||||
ret := _m.Called(ctx, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ReadTree")
|
||||
}
|
||||
|
||||
var r0 []FileTreeEntry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) ([]FileTreeEntry, error)); ok {
|
||||
return rf(ctx, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) []FileTreeEntry); ok {
|
||||
r0 = rf(ctx, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]FileTreeEntry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = rf(ctx, ref)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGitRepository_ReadTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadTree'
|
||||
type MockGitRepository_ReadTree_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ReadTree is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - ref string
|
||||
func (_e *MockGitRepository_Expecter) ReadTree(ctx interface{}, ref interface{}) *MockGitRepository_ReadTree_Call {
|
||||
return &MockGitRepository_ReadTree_Call{Call: _e.mock.On("ReadTree", ctx, ref)}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_ReadTree_Call) Run(run func(ctx context.Context, ref string)) *MockGitRepository_ReadTree_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_ReadTree_Call) Return(_a0 []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 {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Test provides a mock function with given fields: ctx
|
||||
func (_m *MockGitRepository) Test(ctx context.Context) (*v0alpha1.TestResults, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Test")
|
||||
}
|
||||
|
||||
var r0 *v0alpha1.TestResults
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) (*v0alpha1.TestResults, error)); ok {
|
||||
return rf(ctx)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context) *v0alpha1.TestResults); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*v0alpha1.TestResults)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGitRepository_Test_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Test'
|
||||
type MockGitRepository_Test_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Test is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockGitRepository_Expecter) Test(ctx interface{}) *MockGitRepository_Test_Call {
|
||||
return &MockGitRepository_Test_Call{Call: _e.mock.On("Test", ctx)}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Test_Call) Run(run func(ctx context.Context)) *MockGitRepository_Test_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Test_Call) Return(_a0 *v0alpha1.TestResults, _a1 error) *MockGitRepository_Test_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Test_Call) RunAndReturn(run func(context.Context) (*v0alpha1.TestResults, error)) *MockGitRepository_Test_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// URL provides a mock function with no fields
|
||||
func (_m *MockGitRepository) URL() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for URL")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGitRepository_URL_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'URL'
|
||||
type MockGitRepository_URL_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// URL is a helper method to define mock.On call
|
||||
func (_e *MockGitRepository_Expecter) URL() *MockGitRepository_URL_Call {
|
||||
return &MockGitRepository_URL_Call{Call: _e.mock.On("URL")}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_URL_Call) Run(run func()) *MockGitRepository_URL_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_URL_Call) Return(_a0 string) *MockGitRepository_URL_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_URL_Call) RunAndReturn(run func() string) *MockGitRepository_URL_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: ctx, path, ref, data, message
|
||||
func (_m *MockGitRepository) 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 {
|
||||
panic("no return value specified for Update")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok {
|
||||
r0 = rf(ctx, path, ref, data, message)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGitRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
|
||||
type MockGitRepository_Update_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Update is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
// - data []byte
|
||||
// - message string
|
||||
func (_e *MockGitRepository_Expecter) Update(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockGitRepository_Update_Call {
|
||||
return &MockGitRepository_Update_Call{Call: _e.mock.On("Update", ctx, path, ref, data, message)}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Update_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockGitRepository_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 *MockGitRepository_Update_Call) Return(_a0 error) *MockGitRepository_Update_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Update_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockGitRepository_Update_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Validate provides a mock function with no fields
|
||||
func (_m *MockGitRepository) Validate() field.ErrorList {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Validate")
|
||||
}
|
||||
|
||||
var r0 field.ErrorList
|
||||
if rf, ok := ret.Get(0).(func() field.ErrorList); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(field.ErrorList)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGitRepository_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
|
||||
type MockGitRepository_Validate_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Validate is a helper method to define mock.On call
|
||||
func (_e *MockGitRepository_Expecter) Validate() *MockGitRepository_Validate_Call {
|
||||
return &MockGitRepository_Validate_Call{Call: _e.mock.On("Validate")}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Validate_Call) Run(run func()) *MockGitRepository_Validate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Validate_Call) Return(_a0 field.ErrorList) *MockGitRepository_Validate_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Validate_Call) RunAndReturn(run func() field.ErrorList) *MockGitRepository_Validate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Write provides a mock function with given fields: ctx, path, ref, data, message
|
||||
func (_m *MockGitRepository) 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 {
|
||||
panic("no return value specified for Write")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok {
|
||||
r0 = rf(ctx, path, ref, data, message)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGitRepository_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write'
|
||||
type MockGitRepository_Write_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Write is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
// - data []byte
|
||||
// - message string
|
||||
func (_e *MockGitRepository_Expecter) Write(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockGitRepository_Write_Call {
|
||||
return &MockGitRepository_Write_Call{Call: _e.mock.On("Write", ctx, path, ref, data, message)}
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Write_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockGitRepository_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 *MockGitRepository_Write_Call) Return(_a0 error) *MockGitRepository_Write_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGitRepository_Write_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockGitRepository_Write_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockGitRepository creates a new instance of MockGitRepository. 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 NewMockGitRepository(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockGitRepository {
|
||||
mock := &MockGitRepository{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -35,6 +35,8 @@ type githubRepository struct {
|
||||
|
||||
// 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
|
||||
@@ -54,7 +56,7 @@ func NewGitHub(
|
||||
secrets secrets.Service,
|
||||
cloneFn CloneFn,
|
||||
) (GithubRepository, error) {
|
||||
owner, repo, err := parseOwnerRepo(config.Spec.GitHub.URL)
|
||||
owner, repo, err := ParseOwnerRepoGithub(config.Spec.GitHub.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse owner and repo: %w", err)
|
||||
}
|
||||
@@ -104,7 +106,7 @@ func (r *githubRepository) Validate() (list field.ErrorList) {
|
||||
if gh.URL == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github", "url"), "a github url is required"))
|
||||
} else {
|
||||
_, _, err := parseOwnerRepo(gh.URL)
|
||||
_, _, 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/") {
|
||||
@@ -113,7 +115,7 @@ func (r *githubRepository) Validate() (list field.ErrorList) {
|
||||
}
|
||||
if gh.Branch == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github", "branch"), "a github branch is required"))
|
||||
} else if !isValidGitBranchName(gh.Branch) {
|
||||
} 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
|
||||
@@ -132,7 +134,7 @@ func (r *githubRepository) Validate() (list field.ErrorList) {
|
||||
return list
|
||||
}
|
||||
|
||||
func parseOwnerRepo(giturl string) (owner string, repo string, err error) {
|
||||
func ParseOwnerRepoGithub(giturl string) (owner string, repo string, err error) {
|
||||
parsed, e := url.Parse(strings.TrimSuffix(giturl, ".git"))
|
||||
if e != nil {
|
||||
err = e
|
||||
@@ -160,7 +162,7 @@ func (r *githubRepository) Test(ctx context.Context) (*provisioning.TestResults,
|
||||
}
|
||||
|
||||
url := r.config.Spec.GitHub.URL
|
||||
owner, repo, err := parseOwnerRepo(url)
|
||||
owner, repo, err := ParseOwnerRepoGithub(url)
|
||||
if err != nil {
|
||||
return fromFieldError(field.Invalid(
|
||||
field.NewPath("spec", "github", "url"), url, err.Error())), nil
|
||||
@@ -459,7 +461,7 @@ func (r *githubRepository) History(ctx context.Context, path, ref string) ([]pro
|
||||
// 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.
|
||||
// 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.
|
||||
@@ -467,7 +469,7 @@ var basicGitBranchNameRegex = regexp.MustCompile(`^[a-zA-Z0-9\-\_\/\.]+$`)
|
||||
// 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 {
|
||||
func IsValidGitBranchName(branch string) bool {
|
||||
if !basicGitBranchNameRegex.MatchString(branch) {
|
||||
return false
|
||||
}
|
||||
@@ -483,7 +485,7 @@ func isValidGitBranchName(branch string) bool {
|
||||
}
|
||||
|
||||
func (r *githubRepository) ensureBranchExists(ctx context.Context, branchName string) error {
|
||||
if !isValidGitBranchName(branchName) {
|
||||
if !IsValidGitBranchName(branchName) {
|
||||
return &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Code: http.StatusBadRequest,
|
||||
|
||||
@@ -0,0 +1,942 @@
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
github "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
// MockGithubRepository is an autogenerated mock type for the GithubRepository type
|
||||
type MockGithubRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockGithubRepository_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockGithubRepository) EXPECT() *MockGithubRepository_Expecter {
|
||||
return &MockGithubRepository_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Client provides a mock function with no fields
|
||||
func (_m *MockGithubRepository) Client() github.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 {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(github.Client)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGithubRepository_Client_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Client'
|
||||
type MockGithubRepository_Client_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Client is a helper method to define mock.On call
|
||||
func (_e *MockGithubRepository_Expecter) Client() *MockGithubRepository_Client_Call {
|
||||
return &MockGithubRepository_Client_Call{Call: _e.mock.On("Client")}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Client_Call) Run(run func()) *MockGithubRepository_Client_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Client_Call) Return(_a0 github.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 {
|
||||
_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) {
|
||||
ret := _m.Called(ctx, base, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CompareFiles")
|
||||
}
|
||||
|
||||
var r0 []VersionedFileChange
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]VersionedFileChange, error)); ok {
|
||||
return rf(ctx, base, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []VersionedFileChange); ok {
|
||||
r0 = rf(ctx, base, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]VersionedFileChange)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, base, ref)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGithubRepository_CompareFiles_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CompareFiles'
|
||||
type MockGithubRepository_CompareFiles_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CompareFiles is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - base string
|
||||
// - ref string
|
||||
func (_e *MockGithubRepository_Expecter) CompareFiles(ctx interface{}, base interface{}, ref interface{}) *MockGithubRepository_CompareFiles_Call {
|
||||
return &MockGithubRepository_CompareFiles_Call{Call: _e.mock.On("CompareFiles", ctx, base, ref)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_CompareFiles_Call) Run(run func(ctx context.Context, base string, ref string)) *MockGithubRepository_CompareFiles_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_CompareFiles_Call) Return(_a0 []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 {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Config provides a mock function with no fields
|
||||
func (_m *MockGithubRepository) Config() *v0alpha1.Repository {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Config")
|
||||
}
|
||||
|
||||
var r0 *v0alpha1.Repository
|
||||
if rf, ok := ret.Get(0).(func() *v0alpha1.Repository); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*v0alpha1.Repository)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGithubRepository_Config_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Config'
|
||||
type MockGithubRepository_Config_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Config is a helper method to define mock.On call
|
||||
func (_e *MockGithubRepository_Expecter) Config() *MockGithubRepository_Config_Call {
|
||||
return &MockGithubRepository_Config_Call{Call: _e.mock.On("Config")}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Config_Call) Run(run func()) *MockGithubRepository_Config_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Config_Call) Return(_a0 *v0alpha1.Repository) *MockGithubRepository_Config_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Config_Call) RunAndReturn(run func() *v0alpha1.Repository) *MockGithubRepository_Config_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, path, ref, data, message
|
||||
func (_m *MockGithubRepository) 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 {
|
||||
panic("no return value specified for Create")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok {
|
||||
r0 = rf(ctx, path, ref, data, message)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGithubRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
|
||||
type MockGithubRepository_Create_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Create is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
// - data []byte
|
||||
// - message string
|
||||
func (_e *MockGithubRepository_Expecter) Create(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockGithubRepository_Create_Call {
|
||||
return &MockGithubRepository_Create_Call{Call: _e.mock.On("Create", ctx, path, ref, data, message)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Create_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockGithubRepository_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 *MockGithubRepository_Create_Call) Return(_a0 error) *MockGithubRepository_Create_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Create_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockGithubRepository_Create_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, path, ref, message
|
||||
func (_m *MockGithubRepository) Delete(ctx context.Context, path string, ref string, message string) error {
|
||||
ret := _m.Called(ctx, path, ref, message)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Delete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
|
||||
r0 = rf(ctx, path, ref, message)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGithubRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
|
||||
type MockGithubRepository_Delete_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Delete is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
// - message string
|
||||
func (_e *MockGithubRepository_Expecter) Delete(ctx interface{}, path interface{}, ref interface{}, message interface{}) *MockGithubRepository_Delete_Call {
|
||||
return &MockGithubRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, path, ref, message)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Delete_Call) Run(run func(ctx context.Context, path string, ref string, message string)) *MockGithubRepository_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 *MockGithubRepository_Delete_Call) Return(_a0 error) *MockGithubRepository_Delete_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Delete_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockGithubRepository_Delete_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// History provides a mock function with given fields: ctx, path, ref
|
||||
func (_m *MockGithubRepository) History(ctx context.Context, path string, ref string) ([]v0alpha1.HistoryItem, error) {
|
||||
ret := _m.Called(ctx, path, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for History")
|
||||
}
|
||||
|
||||
var r0 []v0alpha1.HistoryItem
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]v0alpha1.HistoryItem, error)); ok {
|
||||
return rf(ctx, path, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []v0alpha1.HistoryItem); ok {
|
||||
r0 = rf(ctx, path, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]v0alpha1.HistoryItem)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, path, ref)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGithubRepository_History_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'History'
|
||||
type MockGithubRepository_History_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// History is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
func (_e *MockGithubRepository_Expecter) History(ctx interface{}, path interface{}, ref interface{}) *MockGithubRepository_History_Call {
|
||||
return &MockGithubRepository_History_Call{Call: _e.mock.On("History", ctx, path, ref)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_History_Call) Run(run func(ctx context.Context, path string, ref string)) *MockGithubRepository_History_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_History_Call) Return(_a0 []v0alpha1.HistoryItem, _a1 error) *MockGithubRepository_History_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_History_Call) RunAndReturn(run func(context.Context, string, string) ([]v0alpha1.HistoryItem, error)) *MockGithubRepository_History_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// LatestRef provides a mock function with given fields: ctx
|
||||
func (_m *MockGithubRepository) LatestRef(ctx context.Context) (string, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for LatestRef")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok {
|
||||
return rf(ctx)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context) string); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGithubRepository_LatestRef_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LatestRef'
|
||||
type MockGithubRepository_LatestRef_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// LatestRef is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockGithubRepository_Expecter) LatestRef(ctx interface{}) *MockGithubRepository_LatestRef_Call {
|
||||
return &MockGithubRepository_LatestRef_Call{Call: _e.mock.On("LatestRef", ctx)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_LatestRef_Call) Run(run func(ctx context.Context)) *MockGithubRepository_LatestRef_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_LatestRef_Call) Return(_a0 string, _a1 error) *MockGithubRepository_LatestRef_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_LatestRef_Call) RunAndReturn(run func(context.Context) (string, error)) *MockGithubRepository_LatestRef_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Owner provides a mock function with no fields
|
||||
func (_m *MockGithubRepository) Owner() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Owner")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGithubRepository_Owner_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Owner'
|
||||
type MockGithubRepository_Owner_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Owner is a helper method to define mock.On call
|
||||
func (_e *MockGithubRepository_Expecter) Owner() *MockGithubRepository_Owner_Call {
|
||||
return &MockGithubRepository_Owner_Call{Call: _e.mock.On("Owner")}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Owner_Call) Run(run func()) *MockGithubRepository_Owner_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Owner_Call) Return(_a0 string) *MockGithubRepository_Owner_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Owner_Call) RunAndReturn(run func() string) *MockGithubRepository_Owner_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Read provides a mock function with given fields: ctx, path, ref
|
||||
func (_m *MockGithubRepository) Read(ctx context.Context, path string, ref string) (*FileInfo, error) {
|
||||
ret := _m.Called(ctx, path, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Read")
|
||||
}
|
||||
|
||||
var r0 *FileInfo
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*FileInfo, error)); ok {
|
||||
return rf(ctx, path, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) *FileInfo); ok {
|
||||
r0 = rf(ctx, path, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*FileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, path, ref)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGithubRepository_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read'
|
||||
type MockGithubRepository_Read_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Read is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
func (_e *MockGithubRepository_Expecter) Read(ctx interface{}, path interface{}, ref interface{}) *MockGithubRepository_Read_Call {
|
||||
return &MockGithubRepository_Read_Call{Call: _e.mock.On("Read", ctx, path, ref)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Read_Call) Run(run func(ctx context.Context, path string, ref string)) *MockGithubRepository_Read_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Read_Call) Return(_a0 *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 {
|
||||
_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) {
|
||||
ret := _m.Called(ctx, ref)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ReadTree")
|
||||
}
|
||||
|
||||
var r0 []FileTreeEntry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) ([]FileTreeEntry, error)); ok {
|
||||
return rf(ctx, ref)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) []FileTreeEntry); ok {
|
||||
r0 = rf(ctx, ref)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]FileTreeEntry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = rf(ctx, ref)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGithubRepository_ReadTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadTree'
|
||||
type MockGithubRepository_ReadTree_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ReadTree is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - ref string
|
||||
func (_e *MockGithubRepository_Expecter) ReadTree(ctx interface{}, ref interface{}) *MockGithubRepository_ReadTree_Call {
|
||||
return &MockGithubRepository_ReadTree_Call{Call: _e.mock.On("ReadTree", ctx, ref)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_ReadTree_Call) Run(run func(ctx context.Context, ref string)) *MockGithubRepository_ReadTree_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_ReadTree_Call) Return(_a0 []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 {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Repo provides a mock function with no fields
|
||||
func (_m *MockGithubRepository) Repo() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Repo")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGithubRepository_Repo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Repo'
|
||||
type MockGithubRepository_Repo_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Repo is a helper method to define mock.On call
|
||||
func (_e *MockGithubRepository_Expecter) Repo() *MockGithubRepository_Repo_Call {
|
||||
return &MockGithubRepository_Repo_Call{Call: _e.mock.On("Repo")}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Repo_Call) Run(run func()) *MockGithubRepository_Repo_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Repo_Call) Return(_a0 string) *MockGithubRepository_Repo_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Repo_Call) RunAndReturn(run func() string) *MockGithubRepository_Repo_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ResourceURLs provides a mock function with given fields: ctx, file
|
||||
func (_m *MockGithubRepository) ResourceURLs(ctx context.Context, file *FileInfo) (*v0alpha1.ResourceURLs, error) {
|
||||
ret := _m.Called(ctx, file)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ResourceURLs")
|
||||
}
|
||||
|
||||
var r0 *v0alpha1.ResourceURLs
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *FileInfo) (*v0alpha1.ResourceURLs, error)); ok {
|
||||
return rf(ctx, file)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *FileInfo) *v0alpha1.ResourceURLs); ok {
|
||||
r0 = rf(ctx, file)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*v0alpha1.ResourceURLs)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *FileInfo) error); ok {
|
||||
r1 = rf(ctx, file)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGithubRepository_ResourceURLs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResourceURLs'
|
||||
type MockGithubRepository_ResourceURLs_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ResourceURLs is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - file *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 {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(*FileInfo))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_ResourceURLs_Call) Return(_a0 *v0alpha1.ResourceURLs, _a1 error) *MockGithubRepository_ResourceURLs_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_ResourceURLs_Call) RunAndReturn(run func(context.Context, *FileInfo) (*v0alpha1.ResourceURLs, error)) *MockGithubRepository_ResourceURLs_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Test provides a mock function with given fields: ctx
|
||||
func (_m *MockGithubRepository) Test(ctx context.Context) (*v0alpha1.TestResults, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Test")
|
||||
}
|
||||
|
||||
var r0 *v0alpha1.TestResults
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) (*v0alpha1.TestResults, error)); ok {
|
||||
return rf(ctx)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context) *v0alpha1.TestResults); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*v0alpha1.TestResults)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockGithubRepository_Test_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Test'
|
||||
type MockGithubRepository_Test_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Test is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockGithubRepository_Expecter) Test(ctx interface{}) *MockGithubRepository_Test_Call {
|
||||
return &MockGithubRepository_Test_Call{Call: _e.mock.On("Test", ctx)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Test_Call) Run(run func(ctx context.Context)) *MockGithubRepository_Test_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Test_Call) Return(_a0 *v0alpha1.TestResults, _a1 error) *MockGithubRepository_Test_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Test_Call) RunAndReturn(run func(context.Context) (*v0alpha1.TestResults, error)) *MockGithubRepository_Test_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: ctx, path, ref, data, message
|
||||
func (_m *MockGithubRepository) 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 {
|
||||
panic("no return value specified for Update")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok {
|
||||
r0 = rf(ctx, path, ref, data, message)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGithubRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
|
||||
type MockGithubRepository_Update_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Update is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
// - data []byte
|
||||
// - message string
|
||||
func (_e *MockGithubRepository_Expecter) Update(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockGithubRepository_Update_Call {
|
||||
return &MockGithubRepository_Update_Call{Call: _e.mock.On("Update", ctx, path, ref, data, message)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Update_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockGithubRepository_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 *MockGithubRepository_Update_Call) Return(_a0 error) *MockGithubRepository_Update_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Update_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockGithubRepository_Update_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Validate provides a mock function with no fields
|
||||
func (_m *MockGithubRepository) Validate() field.ErrorList {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Validate")
|
||||
}
|
||||
|
||||
var r0 field.ErrorList
|
||||
if rf, ok := ret.Get(0).(func() field.ErrorList); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(field.ErrorList)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGithubRepository_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
|
||||
type MockGithubRepository_Validate_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Validate is a helper method to define mock.On call
|
||||
func (_e *MockGithubRepository_Expecter) Validate() *MockGithubRepository_Validate_Call {
|
||||
return &MockGithubRepository_Validate_Call{Call: _e.mock.On("Validate")}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Validate_Call) Run(run func()) *MockGithubRepository_Validate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Validate_Call) Return(_a0 field.ErrorList) *MockGithubRepository_Validate_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Validate_Call) RunAndReturn(run func() field.ErrorList) *MockGithubRepository_Validate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Write provides a mock function with given fields: ctx, path, ref, data, message
|
||||
func (_m *MockGithubRepository) 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 {
|
||||
panic("no return value specified for Write")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok {
|
||||
r0 = rf(ctx, path, ref, data, message)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGithubRepository_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write'
|
||||
type MockGithubRepository_Write_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Write is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
// - data []byte
|
||||
// - message string
|
||||
func (_e *MockGithubRepository_Expecter) Write(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockGithubRepository_Write_Call {
|
||||
return &MockGithubRepository_Write_Call{Call: _e.mock.On("Write", ctx, path, ref, data, message)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Write_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockGithubRepository_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 *MockGithubRepository_Write_Call) Return(_a0 error) *MockGithubRepository_Write_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubRepository_Write_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockGithubRepository_Write_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockGithubRepository creates a new instance of MockGithubRepository. 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 NewMockGithubRepository(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockGithubRepository {
|
||||
mock := &MockGithubRepository{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -212,7 +212,7 @@ func TestIsValidGitBranchName(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, isValidGitBranchName(tt.branch))
|
||||
assert.Equal(t, tt.expected, IsValidGitBranchName(tt.branch))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -661,7 +661,7 @@ func TestGitHubRepository_Test(t *testing.T) {
|
||||
|
||||
// If the config has a different URL, parse and set the owner/repo
|
||||
if tt.config.Spec.GitHub.URL != "https://github.com/grafana/grafana" {
|
||||
owner, githubRepo, _ := parseOwnerRepo(tt.config.Spec.GitHub.URL)
|
||||
owner, githubRepo, _ := ParseOwnerRepoGithub(tt.config.Spec.GitHub.URL)
|
||||
repo.owner = owner
|
||||
repo.repo = githubRepo
|
||||
}
|
||||
|
||||
@@ -0,0 +1,728 @@
|
||||
package nanogit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
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"
|
||||
"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"
|
||||
"github.com/grafana/nanogit/protocol"
|
||||
"github.com/grafana/nanogit/protocol/hash"
|
||||
)
|
||||
|
||||
type RepositoryConfig struct {
|
||||
URL string
|
||||
Branch string
|
||||
Token string
|
||||
EncryptedToken []byte
|
||||
Path string
|
||||
}
|
||||
|
||||
// Make sure all public functions of this struct call the (*gitRepository).logger function, to ensure the Git repo details are included.
|
||||
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)
|
||||
}
|
||||
|
||||
// Create nanogit client with authentication
|
||||
client, err := nanogit.NewHTTPClient(
|
||||
gitConfig.URL,
|
||||
options.WithBasicAuth("git", gitConfig.Token),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create nanogit client: %w", err)
|
||||
}
|
||||
|
||||
return &gitRepository{
|
||||
config: config,
|
||||
gitConfig: gitConfig,
|
||||
client: client,
|
||||
secrets: secrets,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *gitRepository) URL() string {
|
||||
return r.gitConfig.URL
|
||||
}
|
||||
|
||||
func (r *gitRepository) Branch() string {
|
||||
return r.gitConfig.Branch
|
||||
}
|
||||
|
||||
func (r *gitRepository) Config() *provisioning.Repository {
|
||||
return r.config
|
||||
}
|
||||
|
||||
// Validate implements provisioning.Repository.
|
||||
func (r *gitRepository) Validate() (list field.ErrorList) {
|
||||
cfg := r.gitConfig
|
||||
|
||||
t := string(r.config.Spec.Type)
|
||||
if cfg.URL == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", t, "url"), "a git url is required"))
|
||||
} else {
|
||||
if !isValidGitURL(cfg.URL) {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", t, "url"), cfg.URL, "invalid git URL format"))
|
||||
}
|
||||
}
|
||||
if cfg.Branch == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", t, "branch"), "a git branch is required"))
|
||||
} else if !repository.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 err := safepath.IsSafe(cfg.Path); err != nil {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", t, "path"), cfg.Path, err.Error()))
|
||||
}
|
||||
|
||||
if safepath.IsAbs(cfg.Path) {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", t, "path"), cfg.Path, "path must be relative"))
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func isValidGitURL(gitURL string) bool {
|
||||
// Parse URL
|
||||
parsed, err := url.Parse(gitURL)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Must be HTTPS
|
||||
if parsed.Scheme != "https" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Must have a host
|
||||
if parsed.Host == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Must have a path
|
||||
if parsed.Path == "" || parsed.Path == "/" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Test implements provisioning.Repository.
|
||||
func (r *gitRepository) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
||||
ctx, _ = r.logger(ctx, "")
|
||||
|
||||
t := string(r.config.Spec.Type)
|
||||
|
||||
if ok, err := r.client.IsAuthorized(ctx); err != nil || !ok {
|
||||
detail := "not authorized"
|
||||
if err != nil {
|
||||
detail = fmt.Sprintf("failed check if authorized: %v", err)
|
||||
}
|
||||
|
||||
return &provisioning.TestResults{
|
||||
Code: http.StatusBadRequest,
|
||||
Success: false,
|
||||
Errors: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseTypeFieldValueInvalid,
|
||||
Field: field.NewPath("spec", t, "token").String(),
|
||||
Detail: detail,
|
||||
}}}, nil
|
||||
}
|
||||
|
||||
if ok, err := r.client.RepoExists(ctx); err != nil || !ok {
|
||||
detail := "repository not found"
|
||||
if err != nil {
|
||||
detail = fmt.Sprintf("failed check if repository exists: %v", err)
|
||||
}
|
||||
|
||||
return &provisioning.TestResults{
|
||||
Code: http.StatusBadRequest,
|
||||
Success: false,
|
||||
Errors: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseTypeFieldValueInvalid,
|
||||
Field: field.NewPath("spec", t, "url").String(),
|
||||
Detail: detail,
|
||||
}}}, nil
|
||||
}
|
||||
|
||||
// Test basic connectivity by getting the branch reference
|
||||
_, err := r.client.GetRef(ctx, fmt.Sprintf("refs/heads/%s", r.gitConfig.Branch))
|
||||
if err != nil {
|
||||
detail := "branch not found"
|
||||
if errors.Is(err, nanogit.ErrObjectNotFound) {
|
||||
return &provisioning.TestResults{
|
||||
Code: http.StatusBadRequest,
|
||||
Success: false,
|
||||
Errors: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseTypeFieldValueInvalid,
|
||||
Field: field.NewPath("spec", t, "branch").String(),
|
||||
Detail: detail,
|
||||
}}}, nil
|
||||
}
|
||||
|
||||
detail = fmt.Sprintf("failed to check if branch exists: %v", err)
|
||||
|
||||
return &provisioning.TestResults{
|
||||
Code: http.StatusBadRequest,
|
||||
Success: false,
|
||||
Errors: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseTypeFieldValueInvalid,
|
||||
Field: field.NewPath("spec", t, "branch").String(),
|
||||
Detail: detail,
|
||||
}}}, nil
|
||||
}
|
||||
|
||||
return &provisioning.TestResults{
|
||||
Code: http.StatusOK,
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read implements provisioning.Repository.
|
||||
func (r *gitRepository) Read(ctx context.Context, filePath, ref string) (*repository.FileInfo, error) {
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
finalPath := safepath.Join(r.gitConfig.Path, filePath)
|
||||
|
||||
// Resolve ref to commit hash
|
||||
refHash, err := r.resolveRefToHash(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get root hash
|
||||
// TODO: Fix GetTree in nanogit as it does not work commit hash
|
||||
commit, err := r.client.GetCommit(ctx, refHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get commit: %w", err)
|
||||
}
|
||||
|
||||
// Check if the path represents a directory
|
||||
if safepath.IsDir(filePath) {
|
||||
tree, err := r.client.GetTreeByPath(ctx, commit.Tree, finalPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, nanogit.ErrObjectNotFound) {
|
||||
return nil, repository.ErrFileNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("get tree by path: %w", err)
|
||||
}
|
||||
|
||||
return &repository.FileInfo{
|
||||
Path: filePath,
|
||||
Ref: refHash.String(),
|
||||
Hash: tree.Hash.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
blob, err := r.client.GetBlobByPath(ctx, commit.Tree, finalPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, nanogit.ErrObjectNotFound) {
|
||||
return nil, repository.ErrFileNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("read blob: %w", err)
|
||||
}
|
||||
|
||||
return &repository.FileInfo{
|
||||
Path: filePath,
|
||||
Ref: ref,
|
||||
Data: blob.Content,
|
||||
Hash: blob.Hash.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *gitRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
|
||||
// Resolve ref to commit hash
|
||||
refHash, err := r.resolveRefToHash(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get flat tree using nanogit's GetFlatTree
|
||||
tree, err := r.client.GetFlatTree(ctx, refHash)
|
||||
if err != nil {
|
||||
if errors.Is(err, nanogit.ErrObjectNotFound) {
|
||||
return nil, repository.ErrRefNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("get flat tree: %w", err)
|
||||
}
|
||||
|
||||
entries := make([]repository.FileTreeEntry, 0, len(tree.Entries))
|
||||
for _, entry := range tree.Entries {
|
||||
isBlob := entry.Type == protocol.ObjectTypeBlob
|
||||
// Apply path prefix filtering
|
||||
relativePath, err := safepath.RelativeTo(entry.Path, r.gitConfig.Path)
|
||||
if err != nil {
|
||||
// File is outside configured path, skip it
|
||||
continue
|
||||
}
|
||||
|
||||
filePath := relativePath
|
||||
if !isBlob && !safepath.IsDir(filePath) {
|
||||
filePath = filePath + "/"
|
||||
}
|
||||
|
||||
converted := repository.FileTreeEntry{
|
||||
Path: filePath,
|
||||
// TODO: Remove size from repository.FileTreeEntry. We don't need it per se.
|
||||
Size: 0, // FlatTreeEntry doesn't have size, set to 0
|
||||
Hash: entry.Hash.String(),
|
||||
Blob: isBlob,
|
||||
}
|
||||
entries = append(entries, converted)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (r *gitRepository) Create(ctx context.Context, path, ref string, data []byte, comment string) error {
|
||||
if ref == "" {
|
||||
ref = r.gitConfig.Branch
|
||||
}
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
branchRef, err := r.ensureBranchExists(ctx, ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
writer, err := r.client.NewStagedWriter(ctx, branchRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create staged writer: %w", err)
|
||||
}
|
||||
|
||||
if err := r.create(ctx, path, data, writer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.commitAndPush(ctx, writer, comment)
|
||||
}
|
||||
|
||||
func (r *gitRepository) create(ctx context.Context, path string, data []byte, writer nanogit.StagedWriter) error {
|
||||
finalPath := safepath.Join(r.gitConfig.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{}
|
||||
}
|
||||
|
||||
if _, err := writer.CreateBlob(ctx, finalPath, data); err != nil {
|
||||
if errors.Is(err, nanogit.ErrObjectAlreadyExists) {
|
||||
return repository.ErrFileAlreadyExists
|
||||
}
|
||||
|
||||
return fmt.Errorf("create blob: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *gitRepository) Update(ctx context.Context, path, ref string, data []byte, comment string) error {
|
||||
if ref == "" {
|
||||
ref = r.gitConfig.Branch
|
||||
}
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
|
||||
// Check if trying to update a directory
|
||||
if safepath.IsDir(path) {
|
||||
return apierrors.NewBadRequest("cannot update a directory")
|
||||
}
|
||||
|
||||
branchRef, err := r.ensureBranchExists(ctx, ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Create a staged writer
|
||||
writer, err := r.client.NewStagedWriter(ctx, branchRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create staged writer: %w", err)
|
||||
}
|
||||
|
||||
if err := r.update(ctx, path, data, writer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.commitAndPush(ctx, writer, comment)
|
||||
}
|
||||
|
||||
func (r *gitRepository) update(ctx context.Context, path string, data []byte, writer nanogit.StagedWriter) error {
|
||||
// Check if trying to update a directory
|
||||
if safepath.IsDir(path) {
|
||||
return apierrors.NewBadRequest("cannot update a directory")
|
||||
}
|
||||
|
||||
finalPath := safepath.Join(r.gitConfig.Path, path)
|
||||
if _, err := writer.UpdateBlob(ctx, finalPath, data); err != nil {
|
||||
if errors.Is(err, nanogit.ErrObjectNotFound) {
|
||||
return repository.ErrFileNotFound
|
||||
}
|
||||
|
||||
return fmt.Errorf("update blob: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *gitRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||
if ref == "" {
|
||||
ref = r.gitConfig.Branch
|
||||
}
|
||||
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
_, 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 {
|
||||
return r.Update(ctx, path, ref, data, message)
|
||||
}
|
||||
|
||||
return r.Create(ctx, path, ref, data, message)
|
||||
}
|
||||
|
||||
func (r *gitRepository) Delete(ctx context.Context, path, ref, comment string) error {
|
||||
if ref == "" {
|
||||
ref = r.gitConfig.Branch
|
||||
}
|
||||
ctx, _ = r.logger(ctx, ref)
|
||||
|
||||
branchRef, err := r.ensureBranchExists(ctx, ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Create a staged writer
|
||||
writer, err := r.client.NewStagedWriter(ctx, branchRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create staged writer: %w", err)
|
||||
}
|
||||
|
||||
if err := r.delete(ctx, path, writer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.commitAndPush(ctx, writer, comment)
|
||||
}
|
||||
|
||||
func (r *gitRepository) delete(ctx context.Context, path string, writer nanogit.StagedWriter) error {
|
||||
finalPath := safepath.Join(r.gitConfig.Path, path)
|
||||
// Check if it's a directory - use DeleteTree for directories, DeleteBlob for files
|
||||
if safepath.IsDir(path) {
|
||||
if _, err := writer.DeleteTree(ctx, finalPath); err != nil {
|
||||
if errors.Is(err, nanogit.ErrObjectNotFound) {
|
||||
return repository.ErrFileNotFound
|
||||
}
|
||||
return fmt.Errorf("delete tree: %w", err)
|
||||
}
|
||||
} else {
|
||||
if _, err := writer.DeleteBlob(ctx, finalPath); err != nil {
|
||||
if errors.Is(err, nanogit.ErrObjectNotFound) {
|
||||
return repository.ErrFileNotFound
|
||||
}
|
||||
return fmt.Errorf("delete blob: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *gitRepository) History(_ context.Context, _ string, _ string) ([]provisioning.HistoryItem, error) {
|
||||
return nil, &apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotImplemented,
|
||||
Reason: metav1.StatusReasonMethodNotAllowed,
|
||||
Message: "history is not supported for pure git repositories",
|
||||
}}
|
||||
}
|
||||
|
||||
func (r *gitRepository) LatestRef(ctx context.Context) (string, error) {
|
||||
ctx, _ = r.logger(ctx, "")
|
||||
branchRef, err := r.client.GetRef(ctx, fmt.Sprintf("refs/heads/%s", r.gitConfig.Branch))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get branch ref: %w", err)
|
||||
}
|
||||
|
||||
return branchRef.Hash.String(), nil
|
||||
}
|
||||
|
||||
func (r *gitRepository) CompareFiles(ctx context.Context, base, ref string) ([]repository.VersionedFileChange, error) {
|
||||
if base == "" && ref == "" {
|
||||
return nil, fmt.Errorf("base and ref cannot be empty")
|
||||
}
|
||||
if ref == "" {
|
||||
return nil, fmt.Errorf("ref cannot be empty")
|
||||
}
|
||||
|
||||
ctx, logger := r.logger(ctx, ref)
|
||||
|
||||
// Resolve base ref to hash
|
||||
var baseHash hash.Hash
|
||||
if base != "" {
|
||||
var err error
|
||||
baseHash, err = r.resolveRefToHash(ctx, base)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve base ref: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve ref to hash
|
||||
refHash, err := r.resolveRefToHash(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve ref: %w", err)
|
||||
}
|
||||
|
||||
// Get commit hashes for base and ref
|
||||
// Compare commits using nanogit
|
||||
files, err := r.client.CompareCommits(ctx, baseHash, refHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("compare commits: %w", err)
|
||||
}
|
||||
|
||||
changes := make([]repository.VersionedFileChange, 0)
|
||||
for _, f := range files {
|
||||
switch f.Status {
|
||||
case protocol.FileStatusAdded:
|
||||
currentPath, err := safepath.RelativeTo(f.Path, r.gitConfig.Path)
|
||||
if err != nil {
|
||||
// do nothing as it's outside of configured path
|
||||
continue
|
||||
}
|
||||
|
||||
changes = append(changes, repository.VersionedFileChange{
|
||||
Path: currentPath,
|
||||
Ref: ref,
|
||||
Action: repository.FileActionCreated,
|
||||
})
|
||||
case protocol.FileStatusModified:
|
||||
currentPath, err := safepath.RelativeTo(f.Path, r.gitConfig.Path)
|
||||
if err != nil {
|
||||
// do nothing as it's outside of configured path
|
||||
continue
|
||||
}
|
||||
|
||||
changes = append(changes, repository.VersionedFileChange{
|
||||
Path: currentPath,
|
||||
Ref: ref,
|
||||
Action: repository.FileActionUpdated,
|
||||
})
|
||||
case protocol.FileStatusDeleted:
|
||||
currentPath, err := safepath.RelativeTo(f.Path, r.gitConfig.Path)
|
||||
if err != nil {
|
||||
// do nothing as it's outside of configured path
|
||||
continue
|
||||
}
|
||||
|
||||
changes = append(changes, repository.VersionedFileChange{
|
||||
Ref: ref,
|
||||
PreviousRef: base,
|
||||
Path: currentPath,
|
||||
PreviousPath: currentPath,
|
||||
Action: repository.FileActionDeleted,
|
||||
})
|
||||
case protocol.FileStatusTypeChanged:
|
||||
// Handle type changes as modifications
|
||||
currentPath, err := safepath.RelativeTo(f.Path, r.gitConfig.Path)
|
||||
if err != nil {
|
||||
// do nothing as it's outside of configured path
|
||||
continue
|
||||
}
|
||||
|
||||
changes = append(changes, repository.VersionedFileChange{
|
||||
Path: currentPath,
|
||||
Ref: ref,
|
||||
Action: repository.FileActionUpdated,
|
||||
})
|
||||
default:
|
||||
logger.Error("ignore unhandled file", "file", f.Path, "status", string(f.Status))
|
||||
}
|
||||
}
|
||||
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func (r *gitRepository) Clone(ctx context.Context, opts repository.CloneOptions) (repository.ClonedRepository, error) {
|
||||
return NewStagedGitRepository(ctx, r, opts)
|
||||
}
|
||||
|
||||
// resolveRefToHash resolves a ref (branch name or commit hash) to a commit hash
|
||||
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)
|
||||
}
|
||||
|
||||
// Try to parse ref as a hash first
|
||||
refHash, err := hash.FromHex(ref)
|
||||
if err == nil && refHash != nil {
|
||||
// Valid hash, return it
|
||||
return refHash, nil
|
||||
}
|
||||
|
||||
// Not a valid hash, try to resolve as a branch reference
|
||||
branchRef, err := r.client.GetRef(ctx, ref)
|
||||
if err != nil {
|
||||
if errors.Is(err, nanogit.ErrObjectNotFound) {
|
||||
return nil, fmt.Errorf("ref not found: %s: %w", ref, repository.ErrRefNotFound)
|
||||
}
|
||||
return nil, fmt.Errorf("get ref %s: %w", ref, err)
|
||||
}
|
||||
|
||||
return branchRef.Hash, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return nanogit.Ref{}, &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: "invalid branch name",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Check if branch exists by trying to get the branch reference
|
||||
branchRef, err := r.client.GetRef(ctx, fmt.Sprintf("refs/heads/%s", branchName))
|
||||
if err == nil {
|
||||
// Branch exists, return it
|
||||
logging.FromContext(ctx).Info("branch already exists", "branch", branchName)
|
||||
return branchRef, nil
|
||||
}
|
||||
|
||||
// If error is not "ref not found", return the error
|
||||
if !errors.Is(err, nanogit.ErrObjectNotFound) {
|
||||
return nanogit.Ref{}, fmt.Errorf("check branch exists: %w", err)
|
||||
}
|
||||
|
||||
// Branch doesn't exist, create it based on the configured branch
|
||||
srcBranch := r.gitConfig.Branch
|
||||
srcRef, err := r.client.GetRef(ctx, fmt.Sprintf("refs/heads/%s", srcBranch))
|
||||
if err != nil {
|
||||
return nanogit.Ref{}, fmt.Errorf("get source branch ref: %w", err)
|
||||
}
|
||||
|
||||
// Create the new branch reference
|
||||
newRef := nanogit.Ref{
|
||||
Name: fmt.Sprintf("refs/heads/%s", branchName),
|
||||
Hash: srcRef.Hash,
|
||||
}
|
||||
|
||||
if err := r.client.CreateRef(ctx, newRef); err != nil {
|
||||
return nanogit.Ref{}, fmt.Errorf("create branch: %w", err)
|
||||
}
|
||||
|
||||
return newRef, nil
|
||||
}
|
||||
|
||||
// createSignature creates author and committer signatures using the context signature if available,
|
||||
// falling back to default Grafana signature
|
||||
func (r *gitRepository) createSignature(ctx context.Context) (nanogit.Author, nanogit.Committer) {
|
||||
author := nanogit.Author{
|
||||
Name: "Grafana",
|
||||
Email: "noreply@grafana.com",
|
||||
Time: time.Now(),
|
||||
}
|
||||
committer := nanogit.Committer{
|
||||
Name: "Grafana",
|
||||
Email: "noreply@grafana.com",
|
||||
Time: time.Now(),
|
||||
}
|
||||
|
||||
// Use signature from context if available
|
||||
if sig := repository.GetAuthorSignature(ctx); sig != nil && sig.Name != "" {
|
||||
author.Name = sig.Name
|
||||
author.Email = sig.Email
|
||||
author.Time = sig.When
|
||||
committer.Name = sig.Name
|
||||
committer.Email = sig.Email
|
||||
committer.Time = sig.When
|
||||
}
|
||||
|
||||
if author.Time.IsZero() {
|
||||
author.Time = time.Now()
|
||||
committer.Time = time.Now()
|
||||
}
|
||||
|
||||
return author, committer
|
||||
}
|
||||
|
||||
func (r *gitRepository) commit(ctx context.Context, writer nanogit.StagedWriter, comment string) error {
|
||||
author, committer := r.createSignature(ctx)
|
||||
if _, err := writer.Commit(ctx, comment, author, committer); err != nil {
|
||||
return fmt.Errorf("commit changes: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *gitRepository) commitAndPush(ctx context.Context, writer nanogit.StagedWriter, comment string) error {
|
||||
if err := r.commit(ctx, writer, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writer.Push(ctx); err != nil {
|
||||
return fmt.Errorf("push changes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *gitRepository) logger(ctx context.Context, ref string) (context.Context, logging.Logger) {
|
||||
logger := logging.FromContext(ctx)
|
||||
|
||||
type containsGit int
|
||||
var containsGitKey containsGit
|
||||
if ctx.Value(containsGitKey) != nil {
|
||||
return ctx, logging.FromContext(ctx)
|
||||
}
|
||||
|
||||
if ref == "" {
|
||||
ref = r.gitConfig.Branch
|
||||
}
|
||||
logger = logger.With(slog.Group("git_repository", "url", r.gitConfig.URL, "ref", ref, "nanogit", true))
|
||||
ctx = logging.Context(ctx, logger)
|
||||
// We want to ensure we don't add multiple git_repository keys. With doesn't deduplicate the keys...
|
||||
ctx = context.WithValue(ctx, containsGitKey, true)
|
||||
|
||||
ctx = log.ToContext(ctx, logger)
|
||||
|
||||
return ctx, logger
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,125 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package nanogit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||
"github.com/grafana/nanogit"
|
||||
)
|
||||
|
||||
// stagedGitRepository implements repository.ClonedRepository by wrapping a gitRepository
|
||||
// FIXME: this is a hack until we can delete the go-git cloned implementation
|
||||
// once that happens we could do more magic here.
|
||||
type stagedGitRepository struct {
|
||||
*gitRepository
|
||||
opts repository.CloneOptions
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
ref, err := repo.client.GetRef(ctx, "refs/heads/"+repo.gitConfig.Branch)
|
||||
if err != nil {
|
||||
// TODO: opts.CreateIfNotExists doesn't make sense in the context of the staged repository
|
||||
// because we only support the branch that is passed in.
|
||||
// we should probably add branch to the repository.CloneOptions which should be repurposed
|
||||
// as some kind of branch creation options.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
writer, err := repo.client.NewStagedWriter(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build staged writer: %w", err)
|
||||
}
|
||||
|
||||
return &stagedGitRepository{
|
||||
gitRepository: repo,
|
||||
opts: opts,
|
||||
writer: writer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *stagedGitRepository) Read(ctx context.Context, path, ref string) (*repository.FileInfo, error) {
|
||||
if ref != "" && ref != r.gitConfig.Branch {
|
||||
return nil, errors.New("ref is not supported for staged repository")
|
||||
}
|
||||
|
||||
// TODO: the read in the cloned is simplied used to check if a folder exists,
|
||||
// We should fix the usage and the interface so that it's not needed to load the entire blob
|
||||
return r.gitRepository.Read(ctx, path, ref)
|
||||
}
|
||||
|
||||
func (r *stagedGitRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
||||
if ref != "" && ref != r.gitConfig.Branch {
|
||||
return nil, errors.New("ref is not supported for staged repository")
|
||||
}
|
||||
|
||||
ref = ""
|
||||
// TODO: I think we don't need this for cloned repository currently.
|
||||
// we should probably remove it from the interface or construct this tree from the writer itself
|
||||
|
||||
return r.gitRepository.ReadTree(ctx, ref)
|
||||
}
|
||||
|
||||
func (r *stagedGitRepository) Create(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")
|
||||
}
|
||||
|
||||
if err := r.create(ctx, path, data, r.writer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.commit(ctx, r.writer, message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.opts.PushOnWrites {
|
||||
return r.Push(ctx, repository.PushOptions{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := r.update(ctx, path, data, r.writer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.commit(ctx, r.writer, message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.opts.PushOnWrites {
|
||||
return r.Push(ctx, repository.PushOptions{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *stagedGitRepository) Update(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")
|
||||
}
|
||||
|
||||
if safepath.IsDir(path) {
|
||||
return errors.New("cannot update a directory in a staged repository")
|
||||
}
|
||||
|
||||
if err := r.update(ctx, path, data, r.writer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.commit(ctx, r.writer, message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.opts.PushOnWrites {
|
||||
return r.Push(ctx, repository.PushOptions{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *stagedGitRepository) Delete(ctx context.Context, path, ref, message string) error {
|
||||
if ref != "" && ref != r.gitConfig.Branch {
|
||||
return errors.New("ref is not supported for staged repository")
|
||||
}
|
||||
|
||||
if err := r.delete(ctx, path, r.writer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.commit(ctx, r.writer, message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.opts.PushOnWrites {
|
||||
return r.Push(ctx, repository.PushOptions{})
|
||||
}
|
||||
|
||||
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 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
return r.writer.Push(ctx)
|
||||
}
|
||||
|
||||
func (r *stagedGitRepository) Remove(_ context.Context) error {
|
||||
// Since we're using nanogit which doesn't actually clone the repository,
|
||||
// we don't need to do anything here as there's nothing to clean up
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,941 @@
|
||||
package nanogit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
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/hash"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewStagedGitRepository(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeClient)
|
||||
opts repository.CloneOptions
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
name: "succeeds with default options",
|
||||
setupMock: func(mockClient *mocks.FakeClient) {
|
||||
mockClient.GetRefReturns(nanogit.Ref{
|
||||
Name: "refs/heads/main",
|
||||
Hash: hash.Hash{1, 2, 3},
|
||||
}, nil)
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
CreateIfNotExists: false,
|
||||
PushOnWrites: false,
|
||||
},
|
||||
wantError: nil,
|
||||
},
|
||||
{
|
||||
name: "succeeds with BeforeFn",
|
||||
setupMock: func(mockClient *mocks.FakeClient) {
|
||||
mockClient.GetRefReturns(nanogit.Ref{
|
||||
Name: "refs/heads/main",
|
||||
Hash: hash.Hash{1, 2, 3},
|
||||
}, nil)
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
CreateIfNotExists: false,
|
||||
PushOnWrites: false,
|
||||
BeforeFn: func() error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantError: nil,
|
||||
},
|
||||
{
|
||||
name: "succeeds with timeout",
|
||||
setupMock: func(mockClient *mocks.FakeClient) {
|
||||
mockClient.GetRefReturns(nanogit.Ref{
|
||||
Name: "refs/heads/main",
|
||||
Hash: hash.Hash{1, 2, 3},
|
||||
}, nil)
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
CreateIfNotExists: false,
|
||||
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,
|
||||
},
|
||||
wantError: errors.New("ref not found"),
|
||||
},
|
||||
{
|
||||
name: "fails with NewStagedWriter error",
|
||||
setupMock: func(mockClient *mocks.FakeClient) {
|
||||
mockClient.GetRefReturns(nanogit.Ref{
|
||||
Name: "refs/heads/main",
|
||||
Hash: hash.Hash{1, 2, 3},
|
||||
}, nil)
|
||||
mockClient.NewStagedWriterReturns(nil, errors.New("failed to create writer"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
CreateIfNotExists: false,
|
||||
PushOnWrites: false,
|
||||
},
|
||||
wantError: errors.New("build staged writer: failed to create writer"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := &mocks.FakeClient{}
|
||||
tt.setupMock(mockClient)
|
||||
|
||||
gitRepo := &gitRepository{
|
||||
client: mockClient,
|
||||
gitConfig: RepositoryConfig{
|
||||
Branch: "main",
|
||||
URL: "https://git.example.com/repo.git",
|
||||
Token: "token123",
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Type: "test_type",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
stagedRepo, err := NewStagedGitRepository(context.Background(), gitRepo, tt.opts)
|
||||
if tt.wantError != nil {
|
||||
require.EqualError(t, err, tt.wantError.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, stagedRepo)
|
||||
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagedGitRepository_Read(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeClient)
|
||||
path string
|
||||
ref string
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
name: "succeeds with empty ref",
|
||||
setupMock: func(mockClient *mocks.FakeClient) {
|
||||
mockClient.GetRefReturns(nanogit.Ref{
|
||||
Name: "refs/heads/main",
|
||||
Hash: hash.Hash{1, 2, 3},
|
||||
}, nil)
|
||||
mockClient.GetCommitReturns(&nanogit.Commit{
|
||||
Tree: hash.Hash{4, 5, 6},
|
||||
}, nil)
|
||||
mockClient.GetBlobByPathReturns(&nanogit.Blob{
|
||||
Content: []byte("file content"),
|
||||
Hash: hash.Hash{7, 8, 9},
|
||||
}, nil)
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
wantError: nil,
|
||||
},
|
||||
{
|
||||
name: "succeeds with matching ref",
|
||||
setupMock: func(mockClient *mocks.FakeClient) {
|
||||
mockClient.GetRefReturns(nanogit.Ref{
|
||||
Name: "refs/heads/main",
|
||||
Hash: hash.Hash{1, 2, 3},
|
||||
}, nil)
|
||||
mockClient.GetCommitReturns(&nanogit.Commit{
|
||||
Tree: hash.Hash{4, 5, 6},
|
||||
}, nil)
|
||||
mockClient.GetBlobByPathReturns(&nanogit.Blob{
|
||||
Content: []byte("file content"),
|
||||
Hash: hash.Hash{7, 8, 9},
|
||||
}, nil)
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "main",
|
||||
wantError: nil,
|
||||
},
|
||||
{
|
||||
name: "fails with unsupported ref",
|
||||
setupMock: func(_ *mocks.FakeClient) {
|
||||
// No setup needed as error occurs before client calls
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "feature-branch",
|
||||
wantError: errors.New("ref is not supported for staged repository"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := &mocks.FakeClient{}
|
||||
tt.setupMock(mockClient)
|
||||
|
||||
stagedRepo := createTestStagedRepository(mockClient)
|
||||
fileInfo, err := stagedRepo.Read(context.Background(), tt.path, tt.ref)
|
||||
if tt.wantError != nil {
|
||||
require.EqualError(t, err, tt.wantError.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, fileInfo)
|
||||
require.Equal(t, tt.path, fileInfo.Path)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagedGitRepository_ReadTree(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeClient)
|
||||
ref string
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
name: "succeeds with empty ref",
|
||||
setupMock: func(mockClient *mocks.FakeClient) {
|
||||
mockClient.GetRefReturns(nanogit.Ref{
|
||||
Name: "refs/heads/main",
|
||||
Hash: hash.Hash{1, 2, 3},
|
||||
}, nil)
|
||||
mockClient.GetFlatTreeReturns(&nanogit.FlatTree{
|
||||
Entries: []nanogit.FlatTreeEntry{
|
||||
{
|
||||
Path: "configs/test.yaml",
|
||||
Hash: hash.Hash{4, 5, 6},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
},
|
||||
ref: "",
|
||||
wantError: nil,
|
||||
},
|
||||
{
|
||||
name: "fails with unsupported ref",
|
||||
setupMock: func(mockClient *mocks.FakeClient) {
|
||||
// No setup needed as error occurs before client calls
|
||||
},
|
||||
ref: "feature-branch",
|
||||
wantError: errors.New("ref is not supported for staged repository"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := &mocks.FakeClient{}
|
||||
tt.setupMock(mockClient)
|
||||
|
||||
stagedRepo := createTestStagedRepository(mockClient)
|
||||
|
||||
entries, err := stagedRepo.ReadTree(context.Background(), tt.ref)
|
||||
|
||||
if tt.wantError != nil {
|
||||
require.EqualError(t, err, tt.wantError.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, entries)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagedGitRepository_Create(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeStagedWriter)
|
||||
opts repository.CloneOptions
|
||||
path string
|
||||
ref string
|
||||
data []byte
|
||||
message string
|
||||
wantError error
|
||||
expectPush bool
|
||||
}{
|
||||
{
|
||||
name: "succeeds with empty ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
message: "Create test file",
|
||||
wantError: nil,
|
||||
expectPush: false,
|
||||
},
|
||||
{
|
||||
name: "succeeds with matching ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: true,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "main",
|
||||
data: []byte("content"),
|
||||
message: "Create test file",
|
||||
wantError: nil,
|
||||
expectPush: true,
|
||||
},
|
||||
{
|
||||
name: "fails with unsupported ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
// No setup needed as error occurs before writer calls
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "feature-branch",
|
||||
data: []byte("content"),
|
||||
message: "Create test file",
|
||||
wantError: errors.New("ref is not supported for staged repository"),
|
||||
},
|
||||
{
|
||||
name: "fails with create blob error",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.CreateBlobReturns(hash.Hash{}, errors.New("create blob failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
message: "Create test file",
|
||||
wantError: errors.New("create blob: create blob failed"),
|
||||
},
|
||||
{
|
||||
name: "fails with commit error",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
message: "Create test file",
|
||||
wantError: errors.New("commit changes: commit failed"),
|
||||
},
|
||||
{
|
||||
name: "fails with push error",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
mockWriter.PushReturns(errors.New("push failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: true,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
message: "Create test file",
|
||||
wantError: errors.New("push failed"),
|
||||
expectPush: true, // Push is still called even though it fails
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
tt.setupMock(mockWriter)
|
||||
|
||||
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, tt.opts)
|
||||
|
||||
err := stagedRepo.Create(context.Background(), tt.path, tt.ref, tt.data, tt.message)
|
||||
|
||||
if tt.wantError != nil {
|
||||
require.EqualError(t, err, tt.wantError.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Verify push behavior
|
||||
if tt.expectPush {
|
||||
require.Equal(t, 1, mockWriter.PushCallCount())
|
||||
} else if tt.wantError == nil {
|
||||
require.Equal(t, 0, mockWriter.PushCallCount())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagedGitRepository_Write(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeStagedWriter)
|
||||
opts repository.CloneOptions
|
||||
path string
|
||||
ref string
|
||||
data []byte
|
||||
message string
|
||||
fileExists bool
|
||||
wantError error
|
||||
expectPush bool
|
||||
}{
|
||||
{
|
||||
name: "succeeds with empty ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.BlobExistsReturns(false, nil)
|
||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
message: "Write test file",
|
||||
fileExists: false,
|
||||
wantError: nil,
|
||||
expectPush: false,
|
||||
},
|
||||
{
|
||||
name: "succeeds with matching ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.BlobExistsReturns(true, nil)
|
||||
mockWriter.UpdateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: true,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "main",
|
||||
data: []byte("updated content"),
|
||||
message: "Update test file",
|
||||
fileExists: true,
|
||||
wantError: nil,
|
||||
expectPush: true,
|
||||
},
|
||||
{
|
||||
name: "fails with unsupported ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
// No setup needed as error occurs before writer calls
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "feature-branch",
|
||||
data: []byte("content"),
|
||||
message: "Write test file",
|
||||
wantError: errors.New("ref is not supported for staged repository"),
|
||||
},
|
||||
{
|
||||
name: "fails with blob exists check error",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.BlobExistsReturns(false, errors.New("blob exists check failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
message: "Write test file",
|
||||
wantError: errors.New("check if file exists: blob exists check failed"),
|
||||
},
|
||||
{
|
||||
name: "fails with create error",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.BlobExistsReturns(false, nil)
|
||||
mockWriter.CreateBlobReturns(hash.Hash{}, errors.New("create failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
message: "Write test file",
|
||||
wantError: errors.New("create blob: create failed"),
|
||||
},
|
||||
{
|
||||
name: "fails with update error",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.BlobExistsReturns(true, nil)
|
||||
mockWriter.UpdateBlobReturns(hash.Hash{}, errors.New("update failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
message: "Write test file",
|
||||
wantError: errors.New("update blob: update failed"),
|
||||
},
|
||||
{
|
||||
name: "fails with commit error",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.BlobExistsReturns(false, nil)
|
||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
message: "Write test file",
|
||||
wantError: errors.New("commit changes: commit failed"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
tt.setupMock(mockWriter)
|
||||
|
||||
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, tt.opts)
|
||||
|
||||
err := stagedRepo.Write(context.Background(), tt.path, tt.ref, tt.data, tt.message)
|
||||
|
||||
if tt.wantError != nil {
|
||||
require.EqualError(t, err, tt.wantError.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Verify push behavior
|
||||
if tt.expectPush {
|
||||
require.Equal(t, 1, mockWriter.PushCallCount())
|
||||
} else if tt.wantError == nil {
|
||||
require.Equal(t, 0, mockWriter.PushCallCount())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagedGitRepository_Update(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeStagedWriter)
|
||||
opts repository.CloneOptions
|
||||
path string
|
||||
ref string
|
||||
data []byte
|
||||
message string
|
||||
wantError error
|
||||
expectPush bool
|
||||
}{
|
||||
{
|
||||
name: "succeeds with empty ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.UpdateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
data: []byte("updated content"),
|
||||
message: "Update test file",
|
||||
wantError: nil,
|
||||
expectPush: false,
|
||||
},
|
||||
{
|
||||
name: "succeeds with matching ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.UpdateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: true,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "main",
|
||||
data: []byte("updated content"),
|
||||
message: "Update test file",
|
||||
wantError: nil,
|
||||
expectPush: true,
|
||||
},
|
||||
{
|
||||
name: "fails with unsupported ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
// No setup needed as error occurs before writer calls
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "feature-branch",
|
||||
data: []byte("content"),
|
||||
message: "Update test file",
|
||||
wantError: errors.New("ref is not supported for staged repository"),
|
||||
},
|
||||
{
|
||||
name: "fails with directory update",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
// No setup needed as error occurs before writer calls
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "directory/",
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
message: "Update directory",
|
||||
wantError: errors.New("cannot update a directory in a staged repository"),
|
||||
},
|
||||
{
|
||||
name: "fails with update blob error",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.UpdateBlobReturns(hash.Hash{}, errors.New("update blob failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
message: "Update test file",
|
||||
wantError: errors.New("update blob: update blob failed"),
|
||||
},
|
||||
{
|
||||
name: "fails with commit error",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.UpdateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
data: []byte("content"),
|
||||
message: "Update test file",
|
||||
wantError: errors.New("commit changes: commit failed"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
tt.setupMock(mockWriter)
|
||||
|
||||
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, tt.opts)
|
||||
|
||||
err := stagedRepo.Update(context.Background(), tt.path, tt.ref, tt.data, tt.message)
|
||||
|
||||
if tt.wantError != nil {
|
||||
require.EqualError(t, err, tt.wantError.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Verify push behavior
|
||||
if tt.expectPush {
|
||||
require.Equal(t, 1, mockWriter.PushCallCount())
|
||||
} else if tt.wantError == nil {
|
||||
require.Equal(t, 0, mockWriter.PushCallCount())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagedGitRepository_Delete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeStagedWriter)
|
||||
opts repository.CloneOptions
|
||||
path string
|
||||
ref string
|
||||
message string
|
||||
wantError error
|
||||
expectPush bool
|
||||
}{
|
||||
{
|
||||
name: "succeeds with empty ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.DeleteBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
message: "Delete test file",
|
||||
wantError: nil,
|
||||
expectPush: false,
|
||||
},
|
||||
{
|
||||
name: "succeeds with matching ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.DeleteTreeReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: true,
|
||||
},
|
||||
path: "testdir/",
|
||||
ref: "main",
|
||||
message: "Delete test directory",
|
||||
wantError: nil,
|
||||
expectPush: true,
|
||||
},
|
||||
{
|
||||
name: "fails with unsupported ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
// No setup needed as error occurs before writer calls
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "feature-branch",
|
||||
message: "Delete test file",
|
||||
wantError: errors.New("ref is not supported for staged repository"),
|
||||
},
|
||||
{
|
||||
name: "fails with delete blob error",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.DeleteBlobReturns(hash.Hash{}, errors.New("delete blob failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
message: "Delete test file",
|
||||
wantError: errors.New("delete blob: delete blob failed"),
|
||||
},
|
||||
{
|
||||
name: "fails with commit error",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.DeleteBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
||||
},
|
||||
opts: repository.CloneOptions{
|
||||
PushOnWrites: false,
|
||||
},
|
||||
path: "test.yaml",
|
||||
ref: "",
|
||||
message: "Delete test file",
|
||||
wantError: errors.New("commit changes: commit failed"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
tt.setupMock(mockWriter)
|
||||
|
||||
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, tt.opts)
|
||||
|
||||
err := stagedRepo.Delete(context.Background(), tt.path, tt.ref, tt.message)
|
||||
|
||||
if tt.wantError != nil {
|
||||
require.EqualError(t, err, tt.wantError.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Verify push behavior
|
||||
if tt.expectPush {
|
||||
require.Equal(t, 1, mockWriter.PushCallCount())
|
||||
} else if tt.wantError == nil {
|
||||
require.Equal(t, 0, mockWriter.PushCallCount())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagedGitRepository_Push(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(*mocks.FakeStagedWriter)
|
||||
opts repository.PushOptions
|
||||
wantError error
|
||||
expectCalls int
|
||||
}{
|
||||
{
|
||||
name: "succeeds with empty ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.PushOptions{},
|
||||
wantError: nil,
|
||||
expectCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "succeeds with matching ref",
|
||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||
mockWriter.PushReturns(nil)
|
||||
},
|
||||
opts: repository.PushOptions{
|
||||
BeforeFn: func() error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantError: nil,
|
||||
expectCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "succeeds with timeout",
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
tt.setupMock(mockWriter)
|
||||
|
||||
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, repository.CloneOptions{})
|
||||
|
||||
err := stagedRepo.Push(context.Background(), tt.opts)
|
||||
|
||||
if tt.wantError != nil {
|
||||
require.EqualError(t, err, tt.wantError.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
require.Equal(t, tt.expectCalls, mockWriter.PushCallCount())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagedGitRepository_Remove(t *testing.T) {
|
||||
t.Run("succeeds with remove", func(t *testing.T) {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, repository.CloneOptions{})
|
||||
|
||||
err := stagedRepo.Remove(context.Background())
|
||||
|
||||
require.NoError(t, err)
|
||||
// No mock calls expected since Remove is a no-op
|
||||
})
|
||||
}
|
||||
|
||||
// Helper functions for creating test instances
|
||||
|
||||
func createTestStagedRepository(mockClient *mocks.FakeClient) *stagedGitRepository {
|
||||
mockWriter := &mocks.FakeStagedWriter{}
|
||||
return createTestStagedRepositoryWithWriter(mockWriter, repository.CloneOptions{}, mockClient)
|
||||
}
|
||||
|
||||
func createTestStagedRepositoryWithWriter(mockWriter *mocks.FakeStagedWriter, opts repository.CloneOptions, mockClient ...*mocks.FakeClient) *stagedGitRepository {
|
||||
var client nanogit.Client
|
||||
if len(mockClient) > 0 {
|
||||
client = mockClient[0]
|
||||
} else {
|
||||
// Create a default mock client for tests that don't need specific behavior
|
||||
defaultClient := &mocks.FakeClient{}
|
||||
client = defaultClient
|
||||
}
|
||||
|
||||
gitRepo := &gitRepository{
|
||||
client: client,
|
||||
gitConfig: RepositoryConfig{
|
||||
Branch: "main",
|
||||
URL: "https://git.example.com/repo.git",
|
||||
Token: "token123",
|
||||
Path: "configs",
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Type: "test_type",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &stagedGitRepository{
|
||||
gitRepository: gitRepo,
|
||||
opts: opts,
|
||||
writer: mockWriter,
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,20 @@ var ErrFileNotFound error = &apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Message: "file not found",
|
||||
}}
|
||||
|
||||
var ErrRefNotFound error = &apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotFound,
|
||||
Reason: metav1.StatusReasonNotFound,
|
||||
Message: "ref not found",
|
||||
}}
|
||||
|
||||
var ErrFileAlreadyExists error = &apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusConflict,
|
||||
Reason: metav1.StatusReasonAlreadyExists,
|
||||
Message: "file already exists",
|
||||
}}
|
||||
|
||||
type FileInfo struct {
|
||||
// Path to the file on disk.
|
||||
// No leading or trailing slashes will be contained within.
|
||||
|
||||
@@ -6,16 +6,19 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
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/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"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
grafanasecrets "github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -34,6 +37,7 @@ type WebhookExtraBuilder struct {
|
||||
|
||||
func ProvideWebhooks(
|
||||
cfg *setting.Cfg,
|
||||
features featuremgmt.FeatureToggles,
|
||||
// FIXME: use multi-tenant service when one exists. In this state, we can't make this a multi-tenant service!
|
||||
secretsSvc grafanasecrets.Service,
|
||||
ghFactory *github.Factory,
|
||||
@@ -64,6 +68,7 @@ func ProvideWebhooks(
|
||||
pullRequestWorker := pullrequest.NewPullRequestWorker(evaluator, commenter)
|
||||
|
||||
return NewWebhookExtra(
|
||||
features,
|
||||
render,
|
||||
webhook,
|
||||
urlProvider,
|
||||
@@ -80,6 +85,7 @@ func ProvideWebhooks(
|
||||
// WebhookExtra implements the Extra interface for webhooks
|
||||
// to wrap around
|
||||
type WebhookExtra struct {
|
||||
features featuremgmt.FeatureToggles
|
||||
render *renderConnector
|
||||
webhook *webhookConnector
|
||||
urlProvider func(namespace string) string
|
||||
@@ -91,6 +97,7 @@ type WebhookExtra struct {
|
||||
}
|
||||
|
||||
func NewWebhookExtra(
|
||||
features featuremgmt.FeatureToggles,
|
||||
render *renderConnector,
|
||||
webhook *webhookConnector,
|
||||
urlProvider func(namespace string) string,
|
||||
@@ -101,6 +108,7 @@ func NewWebhookExtra(
|
||||
workers []jobs.Worker,
|
||||
) *WebhookExtra {
|
||||
return &WebhookExtra{
|
||||
features: features,
|
||||
render: render,
|
||||
webhook: webhook,
|
||||
urlProvider: urlProvider,
|
||||
@@ -176,11 +184,39 @@ func (e *WebhookExtra) AsRepository(ctx context.Context, r *provisioning.Reposit
|
||||
return gogit.Clone(ctx, e.clonedir, r, opts, e.secrets)
|
||||
}
|
||||
|
||||
basicRepo, err := repository.NewGitHub(ctx, r, e.ghFactory, e.secrets, cloneFn)
|
||||
apiRepo, err := repository.NewGitHub(ctx, r, e.ghFactory, e.secrets, cloneFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
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")
|
||||
|
||||
ghCfg := r.Spec.GitHub
|
||||
if ghCfg == nil {
|
||||
return nil, fmt.Errorf("github configuration is required for nano git")
|
||||
}
|
||||
|
||||
gitCfg := nanogit.RepositoryConfig{
|
||||
URL: ghCfg.URL,
|
||||
Branch: ghCfg.Branch,
|
||||
Path: ghCfg.Path,
|
||||
Token: ghCfg.Token,
|
||||
EncryptedToken: ghCfg.EncryptedToken,
|
||||
}
|
||||
|
||||
nanogitRepo, err := nanogit.NewGitRepository(ctx, e.secrets, r, gitCfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating nanogit repository: %w", err)
|
||||
}
|
||||
|
||||
basicRepo := nanogit.NewGithubRepository(apiRepo, nanogitRepo)
|
||||
|
||||
return NewGithubWebhookRepository(basicRepo, webhookURL, e.secrets), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -351,6 +351,13 @@ 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",
|
||||
|
||||
@@ -45,6 +45,7 @@ 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
|
||||
|
||||
|
@@ -191,6 +191,10 @@ 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"
|
||||
|
||||
@@ -2236,6 +2236,19 @@
|
||||
"codeowner": "@grafana/search-and-storage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "nanoGit",
|
||||
"resourceVersion": "1750087021356",
|
||||
"creationTimestamp": "2025-06-16T15:17:01Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Use experimental git library for provisioning",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/grafana-app-platform-squad",
|
||||
"requiresRestart": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "nestedFolders",
|
||||
|
||||
Reference in New Issue
Block a user