Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
This commit is contained in:
+1
-2
@@ -3462,8 +3462,7 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Do not re-export imported variable (\`./trace\`)", "0"]
|
[0, 0, 0, "Do not re-export imported variable (\`./trace\`)", "0"]
|
||||||
],
|
],
|
||||||
"public/app/plugins/datasource/jaeger/datasource.ts:5381": [
|
"public/app/plugins/datasource/jaeger/datasource.ts:5381": [
|
||||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
|
||||||
],
|
],
|
||||||
"public/app/plugins/datasource/loki/LanguageProvider.ts:5381": [
|
"public/app/plugins/datasource/loki/LanguageProvider.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
outputs:
|
outputs:
|
||||||
changed: ${{ steps.detect-changes.outputs.frontend }}
|
changed: ${{ steps.detect-changes.outputs.frontend }}
|
||||||
|
prettier: ${{ steps.detect-changes.outputs.frontend == 'true' || steps.detect-changes.outputs.docs == 'true' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -34,7 +35,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
# Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`,
|
# Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`,
|
||||||
# the `lint-frontend-prettier-enterprise` workflow will run instead
|
# the `lint-frontend-prettier-enterprise` workflow will run instead
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true && needs.detect-changes.outputs.changed == 'true'
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true && needs.detect-changes.outputs.prettier == 'true'
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -55,7 +56,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
# Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks)
|
# Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks)
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false && needs.detect-changes.outputs.changed == 'true'
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false && needs.detect-changes.outputs.prettier == 'true'
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
+21
-2
@@ -1,3 +1,22 @@
|
|||||||
|
APP_SDK_VERSION := v0.39.2
|
||||||
|
APP_SDK_DIR := $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION)
|
||||||
|
APP_SDK_BIN := $(APP_SDK_DIR)/grafana-app-sdk
|
||||||
|
|
||||||
|
.PHONY: install-app-sdk
|
||||||
|
install-app-sdk: $(APP_SDK_BIN) ## Install the Grafana App SDK
|
||||||
|
|
||||||
|
$(APP_SDK_BIN):
|
||||||
|
@echo "Installing Grafana App SDK version $(APP_SDK_VERSION)"
|
||||||
|
@mkdir -p $(APP_SDK_DIR)
|
||||||
|
# The only way to install specific versions of binaries using `go install`
|
||||||
|
# is by setting GOBIN to the directory you want to install the binary to.
|
||||||
|
GOBIN=$(APP_SDK_DIR) go install github.com/grafana/grafana-app-sdk/cmd/grafana-app-sdk@$(APP_SDK_VERSION)
|
||||||
|
@touch $@
|
||||||
|
|
||||||
|
.PHONY: update-app-sdk
|
||||||
|
update-app-sdk: ## Update the Grafana App SDK dependency in go.mod
|
||||||
|
go get github.com/grafana/grafana-app-sdk@$(APP_SDK_VERSION)
|
||||||
|
|
||||||
.PHONY: generate
|
.PHONY: generate
|
||||||
generate:
|
generate: ## Run Grafana App SDK code generation
|
||||||
@grafana-app-sdk generate -g ./pkg/apis --grouping=group --postprocess --defencoding=none
|
@$(APP_SDK_BIN) generate -g ./pkg/apis --grouping=group --postprocess --defencoding=none
|
||||||
|
|||||||
+6
-6
@@ -3,8 +3,8 @@ module github.com/grafana/grafana/apps/advisor
|
|||||||
go 1.24.4
|
go 1.24.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/grafana/grafana-app-sdk v0.39.0
|
github.com/grafana/grafana-app-sdk v0.39.2
|
||||||
k8s.io/apimachinery v0.33.1
|
k8s.io/apimachinery v0.33.2
|
||||||
k8s.io/klog/v2 v2.130.1
|
k8s.io/klog/v2 v2.130.1
|
||||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
|
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
|
||||||
)
|
)
|
||||||
@@ -33,7 +33,7 @@ require (
|
|||||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/grafana/authlib v0.0.0-20250515162837-2f4a8263eabb // indirect
|
github.com/grafana/authlib v0.0.0-20250515162837-2f4a8263eabb // indirect
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.38.2 // indirect
|
github.com/grafana/grafana-app-sdk/logging v0.39.1 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
@@ -79,9 +79,9 @@ require (
|
|||||||
google.golang.org/protobuf v1.36.6 // indirect
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/api v0.33.1 // indirect
|
k8s.io/api v0.33.2 // indirect
|
||||||
k8s.io/apiextensions-apiserver v0.33.1 // indirect
|
k8s.io/apiextensions-apiserver v0.33.2 // indirect
|
||||||
k8s.io/client-go v0.33.1 // indirect
|
k8s.io/client-go v0.33.2 // indirect
|
||||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
|
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
|
||||||
|
|||||||
@@ -63,11 +63,14 @@ github.com/grafana/grafana-app-sdk v0.30.0/go.mod h1:jhfqNIovb+Mes2vdMf9iMCWQkp1
|
|||||||
github.com/grafana/grafana-app-sdk v0.31.0/go.mod h1:Xw00NL7qpRLo5r3Gn48Bl1Xn2n4eUDI5pYf/wMufKWs=
|
github.com/grafana/grafana-app-sdk v0.31.0/go.mod h1:Xw00NL7qpRLo5r3Gn48Bl1Xn2n4eUDI5pYf/wMufKWs=
|
||||||
github.com/grafana/grafana-app-sdk v0.35.1/go.mod h1:Zx5MkVppYK+ElSDUAR6+fjzOVo6I/cIgk+ty+LmNOxI=
|
github.com/grafana/grafana-app-sdk v0.35.1/go.mod h1:Zx5MkVppYK+ElSDUAR6+fjzOVo6I/cIgk+ty+LmNOxI=
|
||||||
github.com/grafana/grafana-app-sdk v0.39.0/go.mod h1:xRyBQOttgWTc3tGe9pI0upnpEPVhzALf7Mh/61O4zyY=
|
github.com/grafana/grafana-app-sdk v0.39.0/go.mod h1:xRyBQOttgWTc3tGe9pI0upnpEPVhzALf7Mh/61O4zyY=
|
||||||
|
github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8=
|
||||||
|
github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY=
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.29.0 h1:mgbXaAf33aFwqwGVeaX30l8rkeAJH0iACgX5Rn6YkN4=
|
github.com/grafana/grafana-app-sdk/logging v0.29.0 h1:mgbXaAf33aFwqwGVeaX30l8rkeAJH0iACgX5Rn6YkN4=
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.29.0/go.mod h1:xy6ZyVXl50Z3DBDLybvBPphbykPhuVNed/VNmen9DQM=
|
github.com/grafana/grafana-app-sdk/logging v0.29.0/go.mod h1:xy6ZyVXl50Z3DBDLybvBPphbykPhuVNed/VNmen9DQM=
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.30.0/go.mod h1:xy6ZyVXl50Z3DBDLybvBPphbykPhuVNed/VNmen9DQM=
|
github.com/grafana/grafana-app-sdk/logging v0.30.0/go.mod h1:xy6ZyVXl50Z3DBDLybvBPphbykPhuVNed/VNmen9DQM=
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.35.0/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk=
|
github.com/grafana/grafana-app-sdk/logging v0.35.0/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk=
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.38.2/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk=
|
github.com/grafana/grafana-app-sdk/logging v0.38.2/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk=
|
||||||
|
github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
||||||
@@ -302,21 +305,25 @@ k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0=
|
|||||||
k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
|
k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
|
||||||
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
|
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
|
||||||
k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw=
|
k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw=
|
||||||
|
k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
|
||||||
k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0=
|
k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0=
|
||||||
k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw=
|
k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw=
|
||||||
k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto=
|
k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto=
|
||||||
k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss=
|
k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss=
|
||||||
k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA=
|
k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA=
|
||||||
|
k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8=
|
||||||
k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg=
|
k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg=
|
||||||
k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||||
k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||||
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||||
k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||||
|
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||||
k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8=
|
k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8=
|
||||||
k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8=
|
k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8=
|
||||||
k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg=
|
k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg=
|
||||||
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
|
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
|
||||||
k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA=
|
k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA=
|
||||||
|
k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
|
||||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
|
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
|
||||||
|
|||||||
@@ -24,5 +24,8 @@ type CheckMetadata struct {
|
|||||||
|
|
||||||
// NewCheckMetadata creates a new CheckMetadata object.
|
// NewCheckMetadata creates a new CheckMetadata object.
|
||||||
func NewCheckMetadata() *CheckMetadata {
|
func NewCheckMetadata() *CheckMetadata {
|
||||||
return &CheckMetadata{}
|
return &CheckMetadata{
|
||||||
|
Finalizers: []string{},
|
||||||
|
Labels: map[string]string{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,11 @@ import (
|
|||||||
type Check struct {
|
type Check struct {
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
||||||
Spec CheckSpec `json:"spec" yaml:"spec"`
|
|
||||||
CheckStatus CheckStatus `json:"status" yaml:"status"`
|
// Spec is the spec of the Check
|
||||||
|
Spec CheckSpec `json:"spec" yaml:"spec"`
|
||||||
|
|
||||||
|
Status CheckStatus `json:"status" yaml:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Check) GetSpec() any {
|
func (o *Check) GetSpec() any {
|
||||||
@@ -37,14 +40,14 @@ func (o *Check) SetSpec(spec any) error {
|
|||||||
|
|
||||||
func (o *Check) GetSubresources() map[string]any {
|
func (o *Check) GetSubresources() map[string]any {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"status": o.CheckStatus,
|
"status": o.Status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Check) GetSubresource(name string) (any, bool) {
|
func (o *Check) GetSubresource(name string) (any, bool) {
|
||||||
switch name {
|
switch name {
|
||||||
case "status":
|
case "status":
|
||||||
return o.CheckStatus, true
|
return o.Status, true
|
||||||
default:
|
default:
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
@@ -57,7 +60,7 @@ func (o *Check) SetSubresource(name string, value any) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot set status type %#v, not of type CheckStatus", value)
|
return fmt.Errorf("cannot set status type %#v, not of type CheckStatus", value)
|
||||||
}
|
}
|
||||||
o.CheckStatus = cast
|
o.Status = cast
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("subresource '%s' does not exist", name)
|
return fmt.Errorf("subresource '%s' does not exist", name)
|
||||||
@@ -219,6 +222,20 @@ func (o *Check) DeepCopyObject() runtime.Object {
|
|||||||
return o.Copy()
|
return o.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Check) DeepCopy() *Check {
|
||||||
|
cpy := &Check{}
|
||||||
|
o.DeepCopyInto(cpy)
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Check) DeepCopyInto(dst *Check) {
|
||||||
|
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||||
|
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||||
|
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
||||||
|
o.Spec.DeepCopyInto(&dst.Spec)
|
||||||
|
o.Status.DeepCopyInto(&dst.Status)
|
||||||
|
}
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
// Interface compliance compile-time check
|
||||||
var _ resource.Object = &Check{}
|
var _ resource.Object = &Check{}
|
||||||
|
|
||||||
@@ -262,5 +279,41 @@ func (o *CheckList) SetItems(items []resource.Object) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *CheckList) DeepCopy() *CheckList {
|
||||||
|
cpy := &CheckList{}
|
||||||
|
o.DeepCopyInto(cpy)
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *CheckList) DeepCopyInto(dst *CheckList) {
|
||||||
|
resource.CopyObjectInto(dst, o)
|
||||||
|
}
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
// Interface compliance compile-time check
|
||||||
var _ resource.ListObject = &CheckList{}
|
var _ resource.ListObject = &CheckList{}
|
||||||
|
|
||||||
|
// Copy methods for all subresource types
|
||||||
|
|
||||||
|
// DeepCopy creates a full deep copy of Spec
|
||||||
|
func (s *CheckSpec) DeepCopy() *CheckSpec {
|
||||||
|
cpy := &CheckSpec{}
|
||||||
|
s.DeepCopyInto(cpy)
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto deep copies Spec into another Spec object
|
||||||
|
func (s *CheckSpec) DeepCopyInto(dst *CheckSpec) {
|
||||||
|
resource.CopyObjectInto(dst, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy creates a full deep copy of CheckStatus
|
||||||
|
func (s *CheckStatus) DeepCopy() *CheckStatus {
|
||||||
|
cpy := &CheckStatus{}
|
||||||
|
s.DeepCopyInto(cpy)
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto deep copies CheckStatus into another CheckStatus object
|
||||||
|
func (s *CheckStatus) DeepCopyInto(dst *CheckStatus) {
|
||||||
|
resource.CopyObjectInto(dst, s)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,16 +3,18 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
type CheckErrorLink struct {
|
type CheckReport struct {
|
||||||
// URL to a page with more information about the error
|
// Number of elements analyzed
|
||||||
Url string `json:"url"`
|
Count int64 `json:"count"`
|
||||||
// Human readable error message
|
// List of failures
|
||||||
Message string `json:"message"`
|
Failures []CheckReportFailure `json:"failures"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCheckErrorLink creates a new CheckErrorLink object.
|
// NewCheckReport creates a new CheckReport object.
|
||||||
func NewCheckErrorLink() *CheckErrorLink {
|
func NewCheckReport() *CheckReport {
|
||||||
return &CheckErrorLink{}
|
return &CheckReport{
|
||||||
|
Failures: []CheckReportFailure{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
@@ -33,7 +35,22 @@ type CheckReportFailure struct {
|
|||||||
|
|
||||||
// NewCheckReportFailure creates a new CheckReportFailure object.
|
// NewCheckReportFailure creates a new CheckReportFailure object.
|
||||||
func NewCheckReportFailure() *CheckReportFailure {
|
func NewCheckReportFailure() *CheckReportFailure {
|
||||||
return &CheckReportFailure{}
|
return &CheckReportFailure{
|
||||||
|
Links: []CheckErrorLink{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type CheckErrorLink struct {
|
||||||
|
// URL to a page with more information about the error
|
||||||
|
Url string `json:"url"`
|
||||||
|
// Human readable error message
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCheckErrorLink creates a new CheckErrorLink object.
|
||||||
|
func NewCheckErrorLink() *CheckErrorLink {
|
||||||
|
return &CheckErrorLink{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
@@ -56,7 +73,7 @@ func NewCheckstatusOperatorState() *CheckstatusOperatorState {
|
|||||||
|
|
||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
type CheckStatus struct {
|
type CheckStatus struct {
|
||||||
Report CheckV0alpha1StatusReport `json:"report"`
|
Report CheckReport `json:"report"`
|
||||||
// operatorStates is a map of operator ID to operator state evaluations.
|
// operatorStates is a map of operator ID to operator state evaluations.
|
||||||
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
|
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
|
||||||
OperatorStates map[string]CheckstatusOperatorState `json:"operatorStates,omitempty"`
|
OperatorStates map[string]CheckstatusOperatorState `json:"operatorStates,omitempty"`
|
||||||
@@ -67,7 +84,7 @@ type CheckStatus struct {
|
|||||||
// NewCheckStatus creates a new CheckStatus object.
|
// NewCheckStatus creates a new CheckStatus object.
|
||||||
func NewCheckStatus() *CheckStatus {
|
func NewCheckStatus() *CheckStatus {
|
||||||
return &CheckStatus{
|
return &CheckStatus{
|
||||||
Report: *NewCheckV0alpha1StatusReport(),
|
Report: *NewCheckReport(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,16 +104,3 @@ const (
|
|||||||
CheckStatusOperatorStateStateInProgress CheckStatusOperatorStateState = "in_progress"
|
CheckStatusOperatorStateStateInProgress CheckStatusOperatorStateState = "in_progress"
|
||||||
CheckStatusOperatorStateStateFailed CheckStatusOperatorStateState = "failed"
|
CheckStatusOperatorStateStateFailed CheckStatusOperatorStateState = "failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type CheckV0alpha1StatusReport struct {
|
|
||||||
// Number of elements analyzed
|
|
||||||
Count int64 `json:"count"`
|
|
||||||
// List of failures
|
|
||||||
Failures []CheckReportFailure `json:"failures"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCheckV0alpha1StatusReport creates a new CheckV0alpha1StatusReport object.
|
|
||||||
func NewCheckV0alpha1StatusReport() *CheckV0alpha1StatusReport {
|
|
||||||
return &CheckV0alpha1StatusReport{}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -24,5 +24,8 @@ type CheckTypeMetadata struct {
|
|||||||
|
|
||||||
// NewCheckTypeMetadata creates a new CheckTypeMetadata object.
|
// NewCheckTypeMetadata creates a new CheckTypeMetadata object.
|
||||||
func NewCheckTypeMetadata() *CheckTypeMetadata {
|
func NewCheckTypeMetadata() *CheckTypeMetadata {
|
||||||
return &CheckTypeMetadata{}
|
return &CheckTypeMetadata{
|
||||||
|
Finalizers: []string{},
|
||||||
|
Labels: map[string]string{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,11 @@ import (
|
|||||||
type CheckType struct {
|
type CheckType struct {
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
||||||
Spec CheckTypeSpec `json:"spec" yaml:"spec"`
|
|
||||||
CheckTypeStatus CheckTypeStatus `json:"status" yaml:"status"`
|
// Spec is the spec of the CheckType
|
||||||
|
Spec CheckTypeSpec `json:"spec" yaml:"spec"`
|
||||||
|
|
||||||
|
Status CheckTypeStatus `json:"status" yaml:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *CheckType) GetSpec() any {
|
func (o *CheckType) GetSpec() any {
|
||||||
@@ -37,14 +40,14 @@ func (o *CheckType) SetSpec(spec any) error {
|
|||||||
|
|
||||||
func (o *CheckType) GetSubresources() map[string]any {
|
func (o *CheckType) GetSubresources() map[string]any {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"status": o.CheckTypeStatus,
|
"status": o.Status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *CheckType) GetSubresource(name string) (any, bool) {
|
func (o *CheckType) GetSubresource(name string) (any, bool) {
|
||||||
switch name {
|
switch name {
|
||||||
case "status":
|
case "status":
|
||||||
return o.CheckTypeStatus, true
|
return o.Status, true
|
||||||
default:
|
default:
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
@@ -57,7 +60,7 @@ func (o *CheckType) SetSubresource(name string, value any) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot set status type %#v, not of type CheckTypeStatus", value)
|
return fmt.Errorf("cannot set status type %#v, not of type CheckTypeStatus", value)
|
||||||
}
|
}
|
||||||
o.CheckTypeStatus = cast
|
o.Status = cast
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("subresource '%s' does not exist", name)
|
return fmt.Errorf("subresource '%s' does not exist", name)
|
||||||
@@ -219,6 +222,20 @@ func (o *CheckType) DeepCopyObject() runtime.Object {
|
|||||||
return o.Copy()
|
return o.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *CheckType) DeepCopy() *CheckType {
|
||||||
|
cpy := &CheckType{}
|
||||||
|
o.DeepCopyInto(cpy)
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *CheckType) DeepCopyInto(dst *CheckType) {
|
||||||
|
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||||
|
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||||
|
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
||||||
|
o.Spec.DeepCopyInto(&dst.Spec)
|
||||||
|
o.Status.DeepCopyInto(&dst.Status)
|
||||||
|
}
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
// Interface compliance compile-time check
|
||||||
var _ resource.Object = &CheckType{}
|
var _ resource.Object = &CheckType{}
|
||||||
|
|
||||||
@@ -262,5 +279,41 @@ func (o *CheckTypeList) SetItems(items []resource.Object) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *CheckTypeList) DeepCopy() *CheckTypeList {
|
||||||
|
cpy := &CheckTypeList{}
|
||||||
|
o.DeepCopyInto(cpy)
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *CheckTypeList) DeepCopyInto(dst *CheckTypeList) {
|
||||||
|
resource.CopyObjectInto(dst, o)
|
||||||
|
}
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
// Interface compliance compile-time check
|
||||||
var _ resource.ListObject = &CheckTypeList{}
|
var _ resource.ListObject = &CheckTypeList{}
|
||||||
|
|
||||||
|
// Copy methods for all subresource types
|
||||||
|
|
||||||
|
// DeepCopy creates a full deep copy of Spec
|
||||||
|
func (s *CheckTypeSpec) DeepCopy() *CheckTypeSpec {
|
||||||
|
cpy := &CheckTypeSpec{}
|
||||||
|
s.DeepCopyInto(cpy)
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto deep copies Spec into another Spec object
|
||||||
|
func (s *CheckTypeSpec) DeepCopyInto(dst *CheckTypeSpec) {
|
||||||
|
resource.CopyObjectInto(dst, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy creates a full deep copy of CheckTypeStatus
|
||||||
|
func (s *CheckTypeStatus) DeepCopy() *CheckTypeStatus {
|
||||||
|
cpy := &CheckTypeStatus{}
|
||||||
|
s.DeepCopyInto(cpy)
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto deep copies CheckTypeStatus into another CheckTypeStatus object
|
||||||
|
func (s *CheckTypeStatus) DeepCopyInto(dst *CheckTypeStatus) {
|
||||||
|
resource.CopyObjectInto(dst, s)
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,5 +23,7 @@ type CheckTypeSpec struct {
|
|||||||
|
|
||||||
// NewCheckTypeSpec creates a new CheckTypeSpec object.
|
// NewCheckTypeSpec creates a new CheckTypeSpec object.
|
||||||
func NewCheckTypeSpec() *CheckTypeSpec {
|
func NewCheckTypeSpec() *CheckTypeSpec {
|
||||||
return &CheckTypeSpec{}
|
return &CheckTypeSpec{
|
||||||
|
Steps: []CheckTypeStep{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ package v0alpha1
|
|||||||
import "k8s.io/apimachinery/pkg/runtime/schema"
|
import "k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Group is the API group used by all kinds in this package
|
// APIGroup is the API group used by all kinds in this package
|
||||||
Group = "advisor.grafana.app"
|
APIGroup = "advisor.grafana.app"
|
||||||
// Version is the API version used by all kinds in this package
|
// APIVersion is the API version used by all kinds in this package
|
||||||
Version = "v0alpha1"
|
APIVersion = "v0alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package
|
// GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package
|
||||||
GroupVersion = schema.GroupVersion{
|
GroupVersion = schema.GroupVersion{
|
||||||
Group: Group,
|
Group: APIGroup,
|
||||||
Version: Version,
|
Version: APIVersion,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
|||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.Check": schema_pkg_apis_advisor_v0alpha1_Check(ref),
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.Check": schema_pkg_apis_advisor_v0alpha1_Check(ref),
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckErrorLink": schema_pkg_apis_advisor_v0alpha1_CheckErrorLink(ref),
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckErrorLink": schema_pkg_apis_advisor_v0alpha1_CheckErrorLink(ref),
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckList": schema_pkg_apis_advisor_v0alpha1_CheckList(ref),
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckList": schema_pkg_apis_advisor_v0alpha1_CheckList(ref),
|
||||||
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReport": schema_pkg_apis_advisor_v0alpha1_CheckReport(ref),
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReportFailure": schema_pkg_apis_advisor_v0alpha1_CheckReportFailure(ref),
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReportFailure": schema_pkg_apis_advisor_v0alpha1_CheckReportFailure(ref),
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckSpec": schema_pkg_apis_advisor_v0alpha1_CheckSpec(ref),
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckSpec": schema_pkg_apis_advisor_v0alpha1_CheckSpec(ref),
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckStatus": schema_pkg_apis_advisor_v0alpha1_CheckStatus(ref),
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckStatus": schema_pkg_apis_advisor_v0alpha1_CheckStatus(ref),
|
||||||
@@ -24,7 +25,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
|||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStatus": schema_pkg_apis_advisor_v0alpha1_CheckTypeStatus(ref),
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStatus": schema_pkg_apis_advisor_v0alpha1_CheckTypeStatus(ref),
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStep": schema_pkg_apis_advisor_v0alpha1_CheckTypeStep(ref),
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStep": schema_pkg_apis_advisor_v0alpha1_CheckTypeStep(ref),
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypestatusOperatorState": schema_pkg_apis_advisor_v0alpha1_CheckTypestatusOperatorState(ref),
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypestatusOperatorState": schema_pkg_apis_advisor_v0alpha1_CheckTypestatusOperatorState(ref),
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckV0alpha1StatusReport": schema_pkg_apis_advisor_v0alpha1_CheckV0alpha1StatusReport(ref),
|
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckstatusOperatorState": schema_pkg_apis_advisor_v0alpha1_CheckstatusOperatorState(ref),
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckstatusOperatorState": schema_pkg_apis_advisor_v0alpha1_CheckstatusOperatorState(ref),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,9 @@ func schema_pkg_apis_advisor_v0alpha1_Check(ref common.ReferenceCallback) common
|
|||||||
},
|
},
|
||||||
"spec": {
|
"spec": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Default: map[string]interface{}{},
|
Description: "Spec is the spec of the Check",
|
||||||
Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckSpec"),
|
Default: map[string]interface{}{},
|
||||||
|
Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckSpec"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
@@ -153,6 +154,43 @@ func schema_pkg_apis_advisor_v0alpha1_CheckList(ref common.ReferenceCallback) co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func schema_pkg_apis_advisor_v0alpha1_CheckReport(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
|
return common.OpenAPIDefinition{
|
||||||
|
Schema: spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"object"},
|
||||||
|
Properties: map[string]spec.Schema{
|
||||||
|
"count": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Number of elements analyzed",
|
||||||
|
Default: 0,
|
||||||
|
Type: []string{"integer"},
|
||||||
|
Format: "int64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"failures": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "List of failures",
|
||||||
|
Type: []string{"array"},
|
||||||
|
Items: &spec.SchemaOrArray{
|
||||||
|
Schema: &spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Default: map[string]interface{}{},
|
||||||
|
Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReportFailure"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Required: []string{"count", "failures"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Dependencies: []string{
|
||||||
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReportFailure"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func schema_pkg_apis_advisor_v0alpha1_CheckReportFailure(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
func schema_pkg_apis_advisor_v0alpha1_CheckReportFailure(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
return common.OpenAPIDefinition{
|
return common.OpenAPIDefinition{
|
||||||
Schema: spec.Schema{
|
Schema: spec.Schema{
|
||||||
@@ -258,7 +296,7 @@ func schema_pkg_apis_advisor_v0alpha1_CheckStatus(ref common.ReferenceCallback)
|
|||||||
"report": {
|
"report": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Default: map[string]interface{}{},
|
Default: map[string]interface{}{},
|
||||||
Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckV0alpha1StatusReport"),
|
Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReport"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"operatorStates": {
|
"operatorStates": {
|
||||||
@@ -296,7 +334,7 @@ func schema_pkg_apis_advisor_v0alpha1_CheckStatus(ref common.ReferenceCallback)
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dependencies: []string{
|
Dependencies: []string{
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckV0alpha1StatusReport", "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckstatusOperatorState"},
|
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReport", "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckstatusOperatorState"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,8 +366,9 @@ func schema_pkg_apis_advisor_v0alpha1_CheckType(ref common.ReferenceCallback) co
|
|||||||
},
|
},
|
||||||
"spec": {
|
"spec": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Default: map[string]interface{}{},
|
Description: "Spec is the spec of the CheckType",
|
||||||
Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeSpec"),
|
Default: map[string]interface{}{},
|
||||||
|
Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeSpec"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
@@ -566,43 +605,6 @@ func schema_pkg_apis_advisor_v0alpha1_CheckTypestatusOperatorState(ref common.Re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func schema_pkg_apis_advisor_v0alpha1_CheckV0alpha1StatusReport(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
|
||||||
return common.OpenAPIDefinition{
|
|
||||||
Schema: spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"object"},
|
|
||||||
Properties: map[string]spec.Schema{
|
|
||||||
"count": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "Number of elements analyzed",
|
|
||||||
Default: 0,
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int64",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"failures": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Description: "List of failures",
|
|
||||||
Type: []string{"array"},
|
|
||||||
Items: &spec.SchemaOrArray{
|
|
||||||
Schema: &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Default: map[string]interface{}{},
|
|
||||||
Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReportFailure"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Required: []string{"count", "failures"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Dependencies: []string{
|
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReportFailure"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func schema_pkg_apis_advisor_v0alpha1_CheckstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
func schema_pkg_apis_advisor_v0alpha1_CheckstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
return common.OpenAPIDefinition{
|
return common.OpenAPIDefinition{
|
||||||
Schema: spec.Schema{
|
Schema: spec.Schema{
|
||||||
|
|||||||
@@ -7,15 +7,19 @@ package apis
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/app"
|
"github.com/grafana/grafana-app-sdk/app"
|
||||||
|
"github.com/grafana/grafana-app-sdk/resource"
|
||||||
|
|
||||||
|
v0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rawSchemaCheckv0alpha1 = []byte(`{"spec":{"properties":{"data":{"additionalProperties":{"type":"string"},"description":"Generic data input that a check can receive","type":"object"}},"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"},"report":{"properties":{"count":{"description":"Number of elements analyzed","type":"integer"},"failures":{"description":"List of failures","items":{"properties":{"item":{"description":"Human readable identifier of the item that failed","type":"string"},"itemID":{"description":"ID of the item that failed","type":"string"},"links":{"description":"Links to actions that can be taken to resolve the failure","items":{"properties":{"message":{"description":"Human readable error message","type":"string"},"url":{"description":"URL to a page with more information about the error","type":"string"}},"required":["url","message"],"type":"object"},"type":"array"},"moreInfo":{"description":"More information about the failure, not meant to be displayed to the user. Used for LLM suggestions.","type":"string"},"severity":{"description":"Severity of the failure","enum":["high","low"],"type":"string"},"stepID":{"description":"Step ID that the failure is associated with","type":"string"}},"required":["severity","stepID","item","itemID","links"],"type":"object"},"type":"array"}},"required":["count","failures"],"type":"object"}},"required":["report"],"type":"object","x-kubernetes-preserve-unknown-fields":true}}`)
|
rawSchemaCheckv0alpha1 = []byte(`{"spec":{"properties":{"data":{"additionalProperties":{"type":"string"},"description":"Generic data input that a check can receive","type":"object"}},"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"},"report":{"properties":{"count":{"description":"Number of elements analyzed","type":"integer"},"failures":{"description":"List of failures","items":{"properties":{"item":{"description":"Human readable identifier of the item that failed","type":"string"},"itemID":{"description":"ID of the item that failed","type":"string"},"links":{"description":"Links to actions that can be taken to resolve the failure","items":{"properties":{"message":{"description":"Human readable error message","type":"string"},"url":{"description":"URL to a page with more information about the error","type":"string"}},"required":["url","message"],"type":"object"},"type":"array"},"moreInfo":{"description":"More information about the failure, not meant to be displayed to the user. Used for LLM suggestions.","type":"string"},"severity":{"description":"Severity of the failure","enum":["high","low"],"type":"string"},"stepID":{"description":"Step ID that the failure is associated with","type":"string"}},"required":["severity","stepID","item","itemID","links"],"type":"object"},"type":"array"}},"required":["count","failures"],"type":"object"}},"required":["report"],"type":"object"}}`)
|
||||||
versionSchemaCheckv0alpha1 app.VersionSchema
|
versionSchemaCheckv0alpha1 app.VersionSchema
|
||||||
_ = json.Unmarshal(rawSchemaCheckv0alpha1, &versionSchemaCheckv0alpha1)
|
_ = json.Unmarshal(rawSchemaCheckv0alpha1, &versionSchemaCheckv0alpha1)
|
||||||
rawSchemaCheckTypev0alpha1 = []byte(`{"spec":{"properties":{"name":{"type":"string"},"steps":{"items":{"properties":{"description":{"type":"string"},"resolution":{"type":"string"},"stepID":{"type":"string"},"title":{"type":"string"}},"required":["title","description","stepID","resolution"],"type":"object"},"type":"array"}},"required":["name","steps"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object","x-kubernetes-preserve-unknown-fields":true}}`)
|
rawSchemaCheckTypev0alpha1 = []byte(`{"spec":{"properties":{"name":{"type":"string"},"steps":{"items":{"properties":{"description":{"type":"string"},"resolution":{"type":"string"},"stepID":{"type":"string"},"title":{"type":"string"}},"required":["title","description","stepID","resolution"],"type":"object"},"type":"array"}},"required":["name","steps"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`)
|
||||||
versionSchemaCheckTypev0alpha1 app.VersionSchema
|
versionSchemaCheckTypev0alpha1 app.VersionSchema
|
||||||
_ = json.Unmarshal(rawSchemaCheckTypev0alpha1, &versionSchemaCheckTypev0alpha1)
|
_ = json.Unmarshal(rawSchemaCheckTypev0alpha1, &versionSchemaCheckTypev0alpha1)
|
||||||
)
|
)
|
||||||
@@ -58,12 +62,6 @@ var appManifestData = app.ManifestData{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonToMap(j string) map[string]any {
|
|
||||||
m := make(map[string]any)
|
|
||||||
json.Unmarshal([]byte(j), &j)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func LocalManifest() app.Manifest {
|
func LocalManifest() app.Manifest {
|
||||||
return app.NewEmbeddedManifest(appManifestData)
|
return app.NewEmbeddedManifest(appManifestData)
|
||||||
}
|
}
|
||||||
@@ -71,3 +69,15 @@ func LocalManifest() app.Manifest {
|
|||||||
func RemoteManifest() app.Manifest {
|
func RemoteManifest() app.Manifest {
|
||||||
return app.NewAPIServerManifest("advisor")
|
return app.NewAPIServerManifest("advisor")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var kindVersionToGoType = map[string]resource.Kind{
|
||||||
|
"Check/v0alpha1": v0alpha1.CheckKind(),
|
||||||
|
"CheckType/v0alpha1": v0alpha1.CheckTypeKind(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists.
|
||||||
|
// If there is no association for the provided Kind and Version, exists will return false.
|
||||||
|
func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exists bool) {
|
||||||
|
goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)]
|
||||||
|
return goType, exists
|
||||||
|
}
|
||||||
|
|||||||
@@ -106,3 +106,25 @@ func SetStatusAnnotation(ctx context.Context, client resource.Client, obj resour
|
|||||||
}},
|
}},
|
||||||
}, resource.PatchOptions{}, obj)
|
}, resource.PatchOptions{}, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetAnnotations(ctx context.Context, client resource.Client, obj resource.Object, annotations map[string]string) error {
|
||||||
|
return client.PatchInto(ctx, obj.GetStaticMetadata().Identifier(), resource.PatchRequest{
|
||||||
|
Operations: []resource.PatchOperation{{
|
||||||
|
Operation: resource.PatchOpAdd,
|
||||||
|
Path: "/metadata/annotations",
|
||||||
|
Value: annotations,
|
||||||
|
}},
|
||||||
|
}, resource.PatchOptions{}, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetStatus(ctx context.Context, client resource.Client, obj resource.Object, status any) error {
|
||||||
|
return client.PatchInto(ctx, obj.GetStaticMetadata().Identifier(), resource.PatchRequest{
|
||||||
|
Operations: []resource.PatchOperation{{
|
||||||
|
Operation: resource.PatchOpAdd,
|
||||||
|
Path: "/status",
|
||||||
|
Value: status,
|
||||||
|
}},
|
||||||
|
}, resource.PatchOptions{
|
||||||
|
Subresource: "status",
|
||||||
|
}, obj)
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,28 +78,21 @@ func processCheck(ctx context.Context, log logging.Logger, client resource.Clien
|
|||||||
return fmt.Errorf("error running steps: %w", err)
|
return fmt.Errorf("error running steps: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
report := &advisorv0alpha1.CheckV0alpha1StatusReport{
|
report := &advisorv0alpha1.CheckReport{
|
||||||
Failures: failures,
|
Failures: failures,
|
||||||
Count: int64(len(items)),
|
Count: int64(len(items)),
|
||||||
}
|
}
|
||||||
|
c.Status.Report = *report
|
||||||
|
err = checks.SetStatus(ctx, client, obj, c.Status)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Set the status annotation to processed and annotate the steps ignored
|
// Set the status annotation to processed and annotate the steps ignored
|
||||||
annotations := checks.AddAnnotations(ctx, obj, map[string]string{
|
annotations := checks.AddAnnotations(ctx, obj, map[string]string{
|
||||||
checks.StatusAnnotation: checks.StatusAnnotationProcessed,
|
checks.StatusAnnotation: checks.StatusAnnotationProcessed,
|
||||||
checks.IgnoreStepsAnnotationList: checkType.GetAnnotations()[checks.IgnoreStepsAnnotationList],
|
checks.IgnoreStepsAnnotationList: checkType.GetAnnotations()[checks.IgnoreStepsAnnotationList],
|
||||||
})
|
})
|
||||||
return client.PatchInto(ctx, obj.GetStaticMetadata().Identifier(), resource.PatchRequest{
|
return checks.SetAnnotations(ctx, client, obj, annotations)
|
||||||
Operations: []resource.PatchOperation{
|
|
||||||
{
|
|
||||||
Operation: resource.PatchOpAdd,
|
|
||||||
Path: "/status/report",
|
|
||||||
Value: *report,
|
|
||||||
}, {
|
|
||||||
Operation: resource.PatchOpAdd,
|
|
||||||
Path: "/metadata/annotations",
|
|
||||||
Value: annotations,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, resource.PatchOptions{}, obj)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func processCheckRetry(ctx context.Context, log logging.Logger, client resource.Client, typesClient resource.Client, obj resource.Object, check checks.Check) error {
|
func processCheckRetry(ctx context.Context, log logging.Logger, client resource.Client, typesClient resource.Client, obj resource.Object, check checks.Check) error {
|
||||||
@@ -157,7 +150,7 @@ func processCheckRetry(ctx context.Context, log logging.Logger, client resource.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Pull failures from the report for the items to retry
|
// Pull failures from the report for the items to retry
|
||||||
c.CheckStatus.Report.Failures = slices.DeleteFunc(c.CheckStatus.Report.Failures, func(f advisorv0alpha1.CheckReportFailure) bool {
|
c.Status.Report.Failures = slices.DeleteFunc(c.Status.Report.Failures, func(f advisorv0alpha1.CheckReportFailure) bool {
|
||||||
if f.ItemID == itemToRetry {
|
if f.ItemID == itemToRetry {
|
||||||
for _, newFailure := range failures {
|
for _, newFailure := range failures {
|
||||||
if newFailure.StepID == f.StepID {
|
if newFailure.StepID == f.StepID {
|
||||||
@@ -171,19 +164,13 @@ func processCheckRetry(ctx context.Context, log logging.Logger, client resource.
|
|||||||
// Failure not in the list of items to retry, keep it
|
// Failure not in the list of items to retry, keep it
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
err = checks.SetStatus(ctx, client, obj, c.Status)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Delete the retry annotation to mark the check as processed
|
// Delete the retry annotation to mark the check as processed
|
||||||
annotations := checks.DeleteAnnotations(ctx, obj, []string{checks.RetryAnnotation})
|
annotations := checks.DeleteAnnotations(ctx, obj, []string{checks.RetryAnnotation})
|
||||||
return client.PatchInto(ctx, obj.GetStaticMetadata().Identifier(), resource.PatchRequest{
|
return checks.SetAnnotations(ctx, client, obj, annotations)
|
||||||
Operations: []resource.PatchOperation{{
|
|
||||||
Operation: resource.PatchOpAdd,
|
|
||||||
Path: "/status/report",
|
|
||||||
Value: c.CheckStatus.Report,
|
|
||||||
}, {
|
|
||||||
Operation: resource.PatchOpAdd,
|
|
||||||
Path: "/metadata/annotations",
|
|
||||||
Value: annotations,
|
|
||||||
}},
|
|
||||||
}, resource.PatchOptions{}, obj)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runStepsInParallel(ctx context.Context, log logging.Logger, spec *advisorv0alpha1.CheckSpec, steps []checks.Step, items []any) ([]advisorv0alpha1.CheckReportFailure, error) {
|
func runStepsInParallel(ctx context.Context, log logging.Logger, spec *advisorv0alpha1.CheckSpec, steps []checks.Step, items []any) ([]advisorv0alpha1.CheckReportFailure, error) {
|
||||||
|
|||||||
@@ -95,9 +95,9 @@ func TestProcessMultipleCheckItems(t *testing.T) {
|
|||||||
err = processCheck(ctx, logging.DefaultLogger, client, typesClient, obj, check)
|
err = processCheck(ctx, logging.DefaultLogger, client, typesClient, obj, check)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, checks.StatusAnnotationProcessed, obj.GetAnnotations()[checks.StatusAnnotation])
|
assert.Equal(t, checks.StatusAnnotationProcessed, obj.GetAnnotations()[checks.StatusAnnotation])
|
||||||
r := client.lastValue.(advisorv0alpha1.CheckV0alpha1StatusReport)
|
r := client.values[0].(advisorv0alpha1.CheckStatus)
|
||||||
assert.Equal(t, r.Count, int64(100))
|
assert.Equal(t, r.Report.Count, int64(100))
|
||||||
assert.Len(t, r.Failures, 50)
|
assert.Len(t, r.Report.Failures, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessCheck_AlreadyProcessed(t *testing.T) {
|
func TestProcessCheck_AlreadyProcessed(t *testing.T) {
|
||||||
@@ -231,7 +231,7 @@ func TestProcessCheckRetry_SkipMissingItem(t *testing.T) {
|
|||||||
checks.RetryAnnotation: "item",
|
checks.RetryAnnotation: "item",
|
||||||
checks.StatusAnnotation: checks.StatusAnnotationProcessed,
|
checks.StatusAnnotation: checks.StatusAnnotationProcessed,
|
||||||
})
|
})
|
||||||
obj.CheckStatus.Report.Failures = []advisorv0alpha1.CheckReportFailure{
|
obj.Status.Report.Failures = []advisorv0alpha1.CheckReportFailure{
|
||||||
{
|
{
|
||||||
ItemID: "item",
|
ItemID: "item",
|
||||||
StepID: "step",
|
StepID: "step",
|
||||||
@@ -254,7 +254,7 @@ func TestProcessCheckRetry_SkipMissingItem(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, checks.StatusAnnotationProcessed, obj.GetAnnotations()[checks.StatusAnnotation])
|
assert.Equal(t, checks.StatusAnnotationProcessed, obj.GetAnnotations()[checks.StatusAnnotation])
|
||||||
assert.Empty(t, obj.GetAnnotations()[checks.RetryAnnotation])
|
assert.Empty(t, obj.GetAnnotations()[checks.RetryAnnotation])
|
||||||
assert.Empty(t, obj.CheckStatus.Report.Failures)
|
assert.Empty(t, obj.Status.Report.Failures)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessCheckRetry_Success(t *testing.T) {
|
func TestProcessCheckRetry_Success(t *testing.T) {
|
||||||
@@ -263,7 +263,7 @@ func TestProcessCheckRetry_Success(t *testing.T) {
|
|||||||
checks.RetryAnnotation: "item",
|
checks.RetryAnnotation: "item",
|
||||||
checks.StatusAnnotation: checks.StatusAnnotationProcessed,
|
checks.StatusAnnotation: checks.StatusAnnotationProcessed,
|
||||||
})
|
})
|
||||||
obj.CheckStatus.Report.Failures = []advisorv0alpha1.CheckReportFailure{
|
obj.Status.Report.Failures = []advisorv0alpha1.CheckReportFailure{
|
||||||
{
|
{
|
||||||
ItemID: "item",
|
ItemID: "item",
|
||||||
StepID: "step",
|
StepID: "step",
|
||||||
@@ -286,16 +286,16 @@ func TestProcessCheckRetry_Success(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, checks.StatusAnnotationProcessed, obj.GetAnnotations()[checks.StatusAnnotation])
|
assert.Equal(t, checks.StatusAnnotationProcessed, obj.GetAnnotations()[checks.StatusAnnotation])
|
||||||
assert.Empty(t, obj.GetAnnotations()[checks.RetryAnnotation])
|
assert.Empty(t, obj.GetAnnotations()[checks.RetryAnnotation])
|
||||||
assert.Empty(t, obj.CheckStatus.Report.Failures)
|
assert.Empty(t, obj.Status.Report.Failures)
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockClient struct {
|
type mockClient struct {
|
||||||
resource.Client
|
resource.Client
|
||||||
lastValue any
|
values []any
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockClient) PatchInto(ctx context.Context, id resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions, obj resource.Object) error {
|
func (m *mockClient) PatchInto(ctx context.Context, id resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions, obj resource.Object) error {
|
||||||
m.lastValue = req.Operations[0].Value
|
m.values = append(m.values, req.Operations[0].Value)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ RoleSpec: {
|
|||||||
|
|
||||||
// Display name of the role
|
// Display name of the role
|
||||||
title: string
|
title: string
|
||||||
|
description: string
|
||||||
|
|
||||||
version: int
|
version: int
|
||||||
group: string
|
group: string
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ func NewCoreRolespecPermission() *CoreRolespecPermission {
|
|||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
type CoreRoleSpec struct {
|
type CoreRoleSpec struct {
|
||||||
// Display name of the role
|
// Display name of the role
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Version int64 `json:"version"`
|
Description string `json:"description"`
|
||||||
Group string `json:"group"`
|
Version int64 `json:"version"`
|
||||||
|
Group string `json:"group"`
|
||||||
// TODO:
|
// TODO:
|
||||||
// delegatable?: bool
|
// delegatable?: bool
|
||||||
// created?
|
// created?
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ func NewGlobalRolespecPermission() *GlobalRolespecPermission {
|
|||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
type GlobalRoleSpec struct {
|
type GlobalRoleSpec struct {
|
||||||
// Display name of the role
|
// Display name of the role
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Version int64 `json:"version"`
|
Description string `json:"description"`
|
||||||
Group string `json:"group"`
|
Version int64 `json:"version"`
|
||||||
|
Group string `json:"group"`
|
||||||
// TODO:
|
// TODO:
|
||||||
// delegatable?: bool
|
// delegatable?: bool
|
||||||
// created?
|
// created?
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ func NewRolespecPermission() *RolespecPermission {
|
|||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
type RoleSpec struct {
|
type RoleSpec struct {
|
||||||
// Display name of the role
|
// Display name of the role
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Version int64 `json:"version"`
|
Description string `json:"description"`
|
||||||
Group string `json:"group"`
|
Version int64 `json:"version"`
|
||||||
|
Group string `json:"group"`
|
||||||
// TODO:
|
// TODO:
|
||||||
// delegatable?: bool
|
// delegatable?: bool
|
||||||
// created?
|
// created?
|
||||||
|
|||||||
@@ -186,6 +186,13 @@ func schema_pkg_apis_iam_v0alpha1_CoreRoleSpec(ref common.ReferenceCallback) com
|
|||||||
Format: "",
|
Format: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"description": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Default: "",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
"version": {
|
"version": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Default: 0,
|
Default: 0,
|
||||||
@@ -215,7 +222,7 @@ func schema_pkg_apis_iam_v0alpha1_CoreRoleSpec(ref common.ReferenceCallback) com
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Required: []string{"title", "version", "group", "permissions"},
|
Required: []string{"title", "description", "version", "group", "permissions"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dependencies: []string{
|
Dependencies: []string{
|
||||||
@@ -740,6 +747,13 @@ func schema_pkg_apis_iam_v0alpha1_GlobalRoleSpec(ref common.ReferenceCallback) c
|
|||||||
Format: "",
|
Format: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"description": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Default: "",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
"version": {
|
"version": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Default: 0,
|
Default: 0,
|
||||||
@@ -769,7 +783,7 @@ func schema_pkg_apis_iam_v0alpha1_GlobalRoleSpec(ref common.ReferenceCallback) c
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Required: []string{"title", "version", "group", "permissions"},
|
Required: []string{"title", "description", "version", "group", "permissions"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dependencies: []string{
|
Dependencies: []string{
|
||||||
@@ -1600,6 +1614,13 @@ func schema_pkg_apis_iam_v0alpha1_RoleSpec(ref common.ReferenceCallback) common.
|
|||||||
Format: "",
|
Format: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"description": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Default: "",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
"version": {
|
"version": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Default: 0,
|
Default: 0,
|
||||||
@@ -1629,7 +1650,7 @@ func schema_pkg_apis_iam_v0alpha1_RoleSpec(ref common.ReferenceCallback) common.
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Required: []string{"title", "version", "group", "permissions"},
|
Required: []string{"title", "description", "version", "group", "permissions"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dependencies: []string{
|
Dependencies: []string{
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ The following guides will help you get started with Loki:
|
|||||||
|
|
||||||
This data source supports these versions of Loki:
|
This data source supports these versions of Loki:
|
||||||
|
|
||||||
- v2.8+
|
- v2.9+
|
||||||
|
|
||||||
## Adding a data source
|
## Adding a data source
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
|
|||||||
| `pluginsSriChecks` | Enables SRI checks for plugin assets | |
|
| `pluginsSriChecks` | Enables SRI checks for plugin assets | |
|
||||||
| `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | |
|
| `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | |
|
||||||
| `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes |
|
| `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes |
|
||||||
| `jaegerBackendMigration` | Enables querying the Jaeger data source without the proxy | Yes |
|
|
||||||
| `alertingUIOptimizeReducer` | Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query | Yes |
|
| `alertingUIOptimizeReducer` | Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query | Yes |
|
||||||
| `azureMonitorEnableUserAuth` | Enables user auth for Azure Monitor datasource only | Yes |
|
| `azureMonitorEnableUserAuth` | Enables user auth for Azure Monitor datasource only | Yes |
|
||||||
| `alertingNotificationsStepMode` | Enables simplified step mode in the notifications section | Yes |
|
| `alertingNotificationsStepMode` | Enables simplified step mode in the notifications section | Yes |
|
||||||
|
|||||||
@@ -58,8 +58,6 @@ require (
|
|||||||
github.com/fullstorydev/grpchan v1.1.1 // @grafana/grafana-backend-group
|
github.com/fullstorydev/grpchan v1.1.1 // @grafana/grafana-backend-group
|
||||||
github.com/gchaincl/sqlhooks v1.3.0 // @grafana/grafana-search-and-storage
|
github.com/gchaincl/sqlhooks v1.3.0 // @grafana/grafana-search-and-storage
|
||||||
github.com/getkin/kin-openapi v0.132.0 // @grafana/grafana-app-platform-squad
|
github.com/getkin/kin-openapi v0.132.0 // @grafana/grafana-app-platform-squad
|
||||||
github.com/go-git/go-billy/v5 v5.6.2 // @grafana/grafana-app-platform-squad
|
|
||||||
github.com/go-git/go-git/v5 v5.14.0 // @grafana/grafana-app-platform-squad
|
|
||||||
github.com/go-jose/go-jose/v3 v3.0.4 // @grafana/identity-access-team
|
github.com/go-jose/go-jose/v3 v3.0.4 // @grafana/identity-access-team
|
||||||
github.com/go-jose/go-jose/v4 v4.1.0 // indirect; @grafana/identity-access-team
|
github.com/go-jose/go-jose/v4 v4.1.0 // indirect; @grafana/identity-access-team
|
||||||
github.com/go-kit/log v0.2.1 // @grafana/grafana-backend-group
|
github.com/go-kit/log v0.2.1 // @grafana/grafana-backend-group
|
||||||
@@ -87,7 +85,7 @@ require (
|
|||||||
github.com/googleapis/gax-go/v2 v2.14.1 // @grafana/grafana-backend-group
|
github.com/googleapis/gax-go/v2 v2.14.1 // @grafana/grafana-backend-group
|
||||||
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
|
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
|
||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // @grafana/grafana-app-platform-squad
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // @grafana/grafana-app-platform-squad
|
||||||
github.com/grafana/alerting v0.0.0-20250701210250-cea2d1683945 // @grafana/alerting-backend
|
github.com/grafana/alerting v0.0.0-20250709204613-c5c6f9c1653d // @grafana/alerting-backend
|
||||||
github.com/grafana/authlib v0.0.0-20250618124654-54543efcfeed // @grafana/identity-access-team
|
github.com/grafana/authlib v0.0.0-20250618124654-54543efcfeed // @grafana/identity-access-team
|
||||||
github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team
|
github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team
|
||||||
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
|
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
|
||||||
@@ -276,7 +274,6 @@ require (
|
|||||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
|
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
|
||||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||||
github.com/RoaringBitmap/roaring v1.9.3 // indirect
|
github.com/RoaringBitmap/roaring v1.9.3 // indirect
|
||||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
||||||
@@ -348,7 +345,6 @@ require (
|
|||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dennwc/varint v1.0.0 // indirect
|
github.com/dennwc/varint v1.0.0 // indirect
|
||||||
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
|
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
|
||||||
@@ -372,7 +368,6 @@ require (
|
|||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
github.com/gammazero/deque v0.2.1 // indirect
|
github.com/gammazero/deque v0.2.1 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||||
@@ -423,7 +418,6 @@ require (
|
|||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jaegertracing/jaeger-idl v0.5.0 // indirect
|
github.com/jaegertracing/jaeger-idl v0.5.0 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
|
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
|
||||||
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
|
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
|
||||||
github.com/jcmturner/gofork v1.7.6 // indirect
|
github.com/jcmturner/gofork v1.7.6 // indirect
|
||||||
@@ -437,7 +431,6 @@ require (
|
|||||||
github.com/jpillora/backoff v1.0.0 // indirect
|
github.com/jpillora/backoff v1.0.0 // indirect
|
||||||
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect
|
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect
|
||||||
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
|
||||||
github.com/klauspost/asmfmt v1.3.2 // indirect
|
github.com/klauspost/asmfmt v1.3.2 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
@@ -492,7 +485,6 @@ require (
|
|||||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
@@ -519,7 +511,6 @@ require (
|
|||||||
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
|
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
|
||||||
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
|
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
|
||||||
github.com/sony/gobreaker v0.5.0 // indirect
|
github.com/sony/gobreaker v0.5.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/afero v1.12.0 // indirect
|
github.com/spf13/afero v1.12.0 // indirect
|
||||||
@@ -538,7 +529,6 @@ require (
|
|||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
||||||
github.com/yudai/pp v2.0.1+incompatible // indirect
|
github.com/yudai/pp v2.0.1+incompatible // indirect
|
||||||
@@ -586,7 +576,6 @@ require (
|
|||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect
|
gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect
|
||||||
gopkg.in/telebot.v3 v3.2.1 // indirect
|
gopkg.in/telebot.v3 v3.2.1 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
|
||||||
k8s.io/apiextensions-apiserver v0.33.2 // indirect
|
k8s.io/apiextensions-apiserver v0.33.2 // indirect
|
||||||
k8s.io/kms v0.33.2 // indirect
|
k8s.io/kms v0.33.2 // indirect
|
||||||
modernc.org/libc v1.65.0 // indirect
|
modernc.org/libc v1.65.0 // indirect
|
||||||
|
|||||||
@@ -744,7 +744,6 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
|
|||||||
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
|
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
|
||||||
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||||
@@ -803,8 +802,6 @@ github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqR
|
|||||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||||
@@ -1054,8 +1051,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
|
||||||
github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
|
github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
|
||||||
github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg=
|
github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg=
|
||||||
github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc=
|
github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc=
|
||||||
@@ -1200,8 +1195,6 @@ github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7M
|
|||||||
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
|
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
|
||||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
|
||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
|
||||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
|
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
|
||||||
@@ -1211,14 +1204,6 @@ github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3
|
|||||||
github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
|
github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
|
||||||
github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
|
github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
|
||||||
github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
|
github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
|
||||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
|
||||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
|
||||||
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
|
|
||||||
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
|
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
@@ -1586,8 +1571,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg
|
|||||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||||
github.com/grafana/alerting v0.0.0-20250701210250-cea2d1683945 h1:3imTbxFpZSVI6IBIB9mn+Xc40lUweWjfMaBSgXR7rLs=
|
github.com/grafana/alerting v0.0.0-20250709204613-c5c6f9c1653d h1:rtlYpwsE3KDWWCg2kytDw3s5qgpDjG87qh1IixAyNz4=
|
||||||
github.com/grafana/alerting v0.0.0-20250701210250-cea2d1683945/go.mod h1:gtR7agmxVfJOmNKV/n2ZULgOYTYNL+PDKYB5N48tQ7Q=
|
github.com/grafana/alerting v0.0.0-20250709204613-c5c6f9c1653d/go.mod h1:gtR7agmxVfJOmNKV/n2ZULgOYTYNL+PDKYB5N48tQ7Q=
|
||||||
github.com/grafana/authlib v0.0.0-20250618124654-54543efcfeed h1:k5Ng33zE9fCawqfEVybOasXY7/FQD5Qg2J92ePneeVM=
|
github.com/grafana/authlib v0.0.0-20250618124654-54543efcfeed h1:k5Ng33zE9fCawqfEVybOasXY7/FQD5Qg2J92ePneeVM=
|
||||||
github.com/grafana/authlib v0.0.0-20250618124654-54543efcfeed/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI=
|
github.com/grafana/authlib v0.0.0-20250618124654-54543efcfeed/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI=
|
||||||
github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM=
|
github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM=
|
||||||
@@ -1815,8 +1800,6 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
|||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE=
|
github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE=
|
||||||
github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k=
|
github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||||
@@ -1877,8 +1860,6 @@ github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+
|
|||||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
|
||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
|
||||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
@@ -2175,8 +2156,6 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU
|
|||||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
|
||||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
@@ -2352,11 +2331,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
|||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
|
||||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
|
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
|
||||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
@@ -2481,8 +2457,6 @@ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+x
|
|||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
|
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||||
@@ -3542,8 +3516,6 @@ gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs=
|
|||||||
gopkg.in/telebot.v3 v3.2.1/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko=
|
gopkg.in/telebot.v3 v3.2.1/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|||||||
+3
-3
@@ -201,7 +201,7 @@
|
|||||||
"expose-loader": "5.0.1",
|
"expose-loader": "5.0.1",
|
||||||
"fishery": "^2.2.2",
|
"fishery": "^2.2.2",
|
||||||
"fork-ts-checker-webpack-plugin": "9.0.2",
|
"fork-ts-checker-webpack-plugin": "9.0.2",
|
||||||
"glob": "11.0.1",
|
"glob": "11.0.3",
|
||||||
"html-loader": "5.1.0",
|
"html-loader": "5.1.0",
|
||||||
"html-webpack-plugin": "5.6.3",
|
"html-webpack-plugin": "5.6.3",
|
||||||
"http-server": "14.1.1",
|
"http-server": "14.1.1",
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
"jest-watch-typeahead": "^2.2.2",
|
"jest-watch-typeahead": "^2.2.2",
|
||||||
"jimp": "^1.6.0",
|
"jimp": "^1.6.0",
|
||||||
"jsdom-testing-mocks": "^1.13.1",
|
"jsdom-testing-mocks": "^1.13.1",
|
||||||
"lerna": "8.2.1",
|
"lerna": "8.2.3",
|
||||||
"mini-css-extract-plugin": "2.9.2",
|
"mini-css-extract-plugin": "2.9.2",
|
||||||
"msw": "2.10.3",
|
"msw": "2.10.3",
|
||||||
"mutationobserver-shim": "0.3.7",
|
"mutationobserver-shim": "0.3.7",
|
||||||
@@ -227,7 +227,7 @@
|
|||||||
"pa11y-ci": "^3.1.0",
|
"pa11y-ci": "^3.1.0",
|
||||||
"pdf-parse": "^1.1.1",
|
"pdf-parse": "^1.1.1",
|
||||||
"plop": "^4.0.1",
|
"plop": "^4.0.1",
|
||||||
"postcss": "8.5.1",
|
"postcss": "8.5.6",
|
||||||
"postcss-loader": "8.1.1",
|
"postcss-loader": "8.1.1",
|
||||||
"postcss-reporter": "7.1.0",
|
"postcss-reporter": "7.1.0",
|
||||||
"postcss-scss": "4.0.9",
|
"postcss-scss": "4.0.9",
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ export interface TransformerRegistryItem<TOptions = any> extends RegistryItem {
|
|||||||
* Set of categories associated with the transformer
|
* Set of categories associated with the transformer
|
||||||
*/
|
*/
|
||||||
categories?: Set<TransformerCategory>;
|
categories?: Set<TransformerCategory>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of tags associated with the transformer for improved transformation search
|
||||||
|
*/
|
||||||
|
tags?: Set<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TransformerCategory {
|
export enum TransformerCategory {
|
||||||
|
|||||||
@@ -210,10 +210,6 @@ export interface FeatureToggles {
|
|||||||
*/
|
*/
|
||||||
provisioning?: boolean;
|
provisioning?: boolean;
|
||||||
/**
|
/**
|
||||||
* Use experimental git library for provisioning
|
|
||||||
*/
|
|
||||||
nanoGit?: boolean;
|
|
||||||
/**
|
|
||||||
* Start an additional https handler and write kubectl options
|
* Start an additional https handler and write kubectl options
|
||||||
*/
|
*/
|
||||||
grafanaAPIServerEnsureKubectlAccess?: boolean;
|
grafanaAPIServerEnsureKubectlAccess?: boolean;
|
||||||
@@ -738,11 +734,6 @@ export interface FeatureToggles {
|
|||||||
*/
|
*/
|
||||||
crashDetection?: boolean;
|
crashDetection?: boolean;
|
||||||
/**
|
/**
|
||||||
* Enables querying the Jaeger data source without the proxy
|
|
||||||
* @default true
|
|
||||||
*/
|
|
||||||
jaegerBackendMigration?: boolean;
|
|
||||||
/**
|
|
||||||
* Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query
|
* Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ This is a Flamegraph component that is used in Grafana and Pyroscope web app to
|
|||||||
Currently this library exposes single component `Flamegraph` that renders whole visualization used for profiling which contains a header, a table representation of the data and a flamegraph.
|
Currently this library exposes single component `Flamegraph` that renders whole visualization used for profiling which contains a header, a table representation of the data and a flamegraph.
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { Flamegraph } from '@grafana/flamegraph';
|
import { FlameGraph } from '@grafana/flamegraph';
|
||||||
|
|
||||||
<FlameGraph
|
<FlameGraph
|
||||||
getTheme={() => createTheme({ colors: { mode: 'dark' } })}
|
getTheme={() => createTheme({ colors: { mode: 'dark' } })}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"eslint": "9.19.0",
|
"eslint": "9.19.0",
|
||||||
"eslint-webpack-plugin": "4.2.0",
|
"eslint-webpack-plugin": "4.2.0",
|
||||||
"fork-ts-checker-webpack-plugin": "9.0.2",
|
"fork-ts-checker-webpack-plugin": "9.0.2",
|
||||||
"glob": "11.0.1",
|
"glob": "11.0.3",
|
||||||
"imports-loader": "^5.0.0",
|
"imports-loader": "^5.0.0",
|
||||||
"replace-in-file-webpack-plugin": "1.0.6",
|
"replace-in-file-webpack-plugin": "1.0.6",
|
||||||
"swc-loader": "0.2.6",
|
"swc-loader": "0.2.6",
|
||||||
|
|||||||
@@ -36,8 +36,22 @@ const (
|
|||||||
serviceNameForProvisioning = "provisioning"
|
serviceNameForProvisioning = "provisioning"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newInternalIdentity(name string, namespace string, orgID int64) Requester {
|
type IdentityOpts func(*StaticRequester)
|
||||||
return &StaticRequester{
|
|
||||||
|
// WithServiceIdentityName sets the `StaticRequester.AccessTokenClaims.Rest.ServiceIdentity` field to the provided name.
|
||||||
|
// This is so far only used by Secrets Manager to identify and gate the service decrypting a secret.
|
||||||
|
func WithServiceIdentityName(name string) IdentityOpts {
|
||||||
|
return func(r *StaticRequester) {
|
||||||
|
r.AccessTokenClaims.Rest.ServiceIdentity = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInternalIdentity(name string, namespace string, orgID int64, opts ...IdentityOpts) Requester {
|
||||||
|
// Create a copy of the ServiceIdentityClaims to avoid modifying the global one.
|
||||||
|
// Some of the options might mutate it.
|
||||||
|
claimsCopy := *ServiceIdentityClaims
|
||||||
|
|
||||||
|
staticRequester := &StaticRequester{
|
||||||
Type: types.TypeAccessPolicy,
|
Type: types.TypeAccessPolicy,
|
||||||
Name: name,
|
Name: name,
|
||||||
UserUID: name,
|
UserUID: name,
|
||||||
@@ -50,37 +64,43 @@ func newInternalIdentity(name string, namespace string, orgID int64) Requester {
|
|||||||
Permissions: map[int64]map[string][]string{
|
Permissions: map[int64]map[string][]string{
|
||||||
orgID: serviceIdentityPermissions,
|
orgID: serviceIdentityPermissions,
|
||||||
},
|
},
|
||||||
AccessTokenClaims: ServiceIdentityClaims,
|
AccessTokenClaims: &claimsCopy,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(staticRequester)
|
||||||
|
}
|
||||||
|
|
||||||
|
return staticRequester
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithServiceIdentity sets an identity representing the service itself in provided org and store it in context.
|
// WithServiceIdentity sets an identity representing the service itself in provided org and store it in context.
|
||||||
// This is useful for background tasks that has to communicate with unfied storage. It also returns a Requester with
|
// This is useful for background tasks that has to communicate with unfied storage. It also returns a Requester with
|
||||||
// static permissions so it can be used in legacy code paths.
|
// static permissions so it can be used in legacy code paths.
|
||||||
func WithServiceIdentity(ctx context.Context, orgID int64) (context.Context, Requester) {
|
func WithServiceIdentity(ctx context.Context, orgID int64, opts ...IdentityOpts) (context.Context, Requester) {
|
||||||
r := newInternalIdentity(serviceName, "*", orgID)
|
r := newInternalIdentity(serviceName, "*", orgID, opts...)
|
||||||
return WithRequester(ctx, r), r
|
return WithRequester(ctx, r), r
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithProvisioningIdentity(ctx context.Context, namespace string) (context.Context, Requester, error) {
|
func WithProvisioningIdentity(ctx context.Context, namespace string, opts ...IdentityOpts) (context.Context, Requester, error) {
|
||||||
ns, err := types.ParseNamespace(namespace)
|
ns, err := types.ParseNamespace(namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
r := newInternalIdentity(serviceNameForProvisioning, ns.Value, ns.OrgID)
|
r := newInternalIdentity(serviceNameForProvisioning, ns.Value, ns.OrgID, opts...)
|
||||||
return WithRequester(ctx, r), r, nil
|
return WithRequester(ctx, r), r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithServiceIdentityContext sets an identity representing the service itself in context.
|
// WithServiceIdentityContext sets an identity representing the service itself in context.
|
||||||
func WithServiceIdentityContext(ctx context.Context, orgID int64) context.Context {
|
func WithServiceIdentityContext(ctx context.Context, orgID int64, opts ...IdentityOpts) context.Context {
|
||||||
ctx, _ = WithServiceIdentity(ctx, orgID)
|
ctx, _ = WithServiceIdentity(ctx, orgID, opts...)
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithServiceIdentityFN calls provided closure with an context contaning the identity of the service.
|
// WithServiceIdentityFN calls provided closure with an context contaning the identity of the service.
|
||||||
func WithServiceIdentityFn[T any](ctx context.Context, orgID int64, fn func(ctx context.Context) (T, error)) (T, error) {
|
func WithServiceIdentityFn[T any](ctx context.Context, orgID int64, fn func(ctx context.Context) (T, error), opts ...IdentityOpts) (T, error) {
|
||||||
return fn(WithServiceIdentityContext(ctx, orgID))
|
return fn(WithServiceIdentityContext(ctx, orgID, opts...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWildcardPermissions(actions ...string) map[string][]string {
|
func getWildcardPermissions(actions ...string) map[string][]string {
|
||||||
@@ -91,14 +111,6 @@ func getWildcardPermissions(actions ...string) map[string][]string {
|
|||||||
return permissions
|
return permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTokenPermissions(groups ...string) []string {
|
|
||||||
out := make([]string, 0, len(groups))
|
|
||||||
for _, group := range groups {
|
|
||||||
out = append(out, group+":*")
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// serviceIdentityPermissions is a list of wildcard permissions for provided actions.
|
// serviceIdentityPermissions is a list of wildcard permissions for provided actions.
|
||||||
// We should add every action required "internally" here.
|
// We should add every action required "internally" here.
|
||||||
var serviceIdentityPermissions = getWildcardPermissions(
|
var serviceIdentityPermissions = getWildcardPermissions(
|
||||||
@@ -121,13 +133,16 @@ var serviceIdentityPermissions = getWildcardPermissions(
|
|||||||
"serviceaccounts:read", // serviceaccounts.ActionRead,
|
"serviceaccounts:read", // serviceaccounts.ActionRead,
|
||||||
)
|
)
|
||||||
|
|
||||||
var serviceIdentityTokenPermissions = getTokenPermissions(
|
var serviceIdentityTokenPermissions = []string{
|
||||||
"folder.grafana.app",
|
"folder.grafana.app:*",
|
||||||
"dashboard.grafana.app",
|
"dashboard.grafana.app:*",
|
||||||
"secret.grafana.app",
|
"secret.grafana.app:*",
|
||||||
"query.grafana.app",
|
"query.grafana.app:*",
|
||||||
"iam.grafana.app",
|
"iam.grafana.app:*",
|
||||||
)
|
|
||||||
|
// Secrets Manager uses a custom verb for secret decryption, and its authorizer does not allow wildcard permissions.
|
||||||
|
"secret.grafana.app/securevalues:decrypt",
|
||||||
|
}
|
||||||
|
|
||||||
var ServiceIdentityClaims = &authn.Claims[authn.AccessTokenClaims]{
|
var ServiceIdentityClaims = &authn.Claims[authn.AccessTokenClaims]{
|
||||||
Rest: authn.AccessTokenClaims{
|
Rest: authn.AccessTokenClaims{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/authlib/authn"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
@@ -24,3 +25,48 @@ func TestRequesterFromContext(t *testing.T) {
|
|||||||
require.Equal(t, expected.GetUID(), actual.GetUID())
|
require.Equal(t, expected.GetUID(), actual.GetUID())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWithServiceIdentity(t *testing.T) {
|
||||||
|
t.Run("with a custom service identity name", func(t *testing.T) {
|
||||||
|
customName := "custom-service"
|
||||||
|
orgID := int64(1)
|
||||||
|
ctx, requester := identity.WithServiceIdentity(context.Background(), orgID, identity.WithServiceIdentityName(customName))
|
||||||
|
require.NotNil(t, requester)
|
||||||
|
require.Equal(t, orgID, requester.GetOrgID())
|
||||||
|
require.Equal(t, customName, requester.GetExtra()[string(authn.ServiceIdentityKey)][0])
|
||||||
|
require.Contains(t, requester.GetTokenPermissions(), "secret.grafana.app/securevalues:decrypt")
|
||||||
|
|
||||||
|
fromCtx, err := identity.GetRequester(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, customName, fromCtx.GetExtra()[string(authn.ServiceIdentityKey)][0])
|
||||||
|
|
||||||
|
// Reuse the context but create another identity on top with a different name and org ID
|
||||||
|
anotherCustomName := "another-custom-service"
|
||||||
|
anotherOrgID := int64(2)
|
||||||
|
ctx2 := identity.WithServiceIdentityContext(ctx, anotherOrgID, identity.WithServiceIdentityName(anotherCustomName))
|
||||||
|
|
||||||
|
fromCtx, err = identity.GetRequester(ctx2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, anotherOrgID, fromCtx.GetOrgID())
|
||||||
|
require.Equal(t, anotherCustomName, fromCtx.GetExtra()[string(authn.ServiceIdentityKey)][0])
|
||||||
|
|
||||||
|
// Reuse the context but create another identity without a custom name
|
||||||
|
ctx3, requester := identity.WithServiceIdentity(ctx2, 1)
|
||||||
|
require.NotNil(t, requester)
|
||||||
|
require.Empty(t, requester.GetExtra()[string(authn.ServiceIdentityKey)])
|
||||||
|
|
||||||
|
fromCtx, err = identity.GetRequester(ctx3)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, fromCtx.GetExtra()[string(authn.ServiceIdentityKey)])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a custom service identity name", func(t *testing.T) {
|
||||||
|
ctx, requester := identity.WithServiceIdentity(context.Background(), 1)
|
||||||
|
require.NotNil(t, requester)
|
||||||
|
require.Empty(t, requester.GetExtra()[string(authn.ServiceIdentityKey)])
|
||||||
|
|
||||||
|
fromCtx, err := identity.GetRequester(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, fromCtx.GetExtra()[string(authn.ServiceIdentityKey)])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -128,12 +128,6 @@ func (c *filesConnector) Connect(ctx context.Context, name string, opts runtime.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement folder delete
|
|
||||||
if r.Method == http.MethodDelete && isDir {
|
|
||||||
responder.Error(apierrors.NewBadRequest("folder navigation not yet supported"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var obj *provisioning.ResourceWrapper
|
var obj *provisioning.ResourceWrapper
|
||||||
code := http.StatusOK
|
code := http.StatusOK
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
package export
|
package export
|
||||||
|
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
|
||||||
|
|
||||||
package export
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
|
|
||||||
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockWrapWithCloneFn is an autogenerated mock type for the WrapWithCloneFn type
|
|
||||||
type MockWrapWithCloneFn struct {
|
|
||||||
mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockWrapWithCloneFn_Expecter struct {
|
|
||||||
mock *mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_m *MockWrapWithCloneFn) EXPECT() *MockWrapWithCloneFn_Expecter {
|
|
||||||
return &MockWrapWithCloneFn_Expecter{mock: &_m.Mock}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute provides a mock function with given fields: ctx, repo, cloneOptions, pushOptions, fn
|
|
||||||
func (_m *MockWrapWithCloneFn) Execute(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
|
||||||
ret := _m.Called(ctx, repo, cloneOptions, pushOptions, fn)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for Execute")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, repository.Repository, repository.CloneOptions, repository.PushOptions, func(repository.Repository, bool) error) error); ok {
|
|
||||||
r0 = rf(ctx, repo, cloneOptions, pushOptions, fn)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockWrapWithCloneFn_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute'
|
|
||||||
type MockWrapWithCloneFn_Execute_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - repo repository.Repository
|
|
||||||
// - cloneOptions repository.CloneOptions
|
|
||||||
// - pushOptions repository.PushOptions
|
|
||||||
// - fn func(repository.Repository , bool) error
|
|
||||||
func (_e *MockWrapWithCloneFn_Expecter) Execute(ctx interface{}, repo interface{}, cloneOptions interface{}, pushOptions interface{}, fn interface{}) *MockWrapWithCloneFn_Execute_Call {
|
|
||||||
return &MockWrapWithCloneFn_Execute_Call{Call: _e.mock.On("Execute", ctx, repo, cloneOptions, pushOptions, fn)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWrapWithCloneFn_Execute_Call) Run(run func(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repository.Repository, bool) error)) *MockWrapWithCloneFn_Execute_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(repository.Repository), args[2].(repository.CloneOptions), args[3].(repository.PushOptions), args[4].(func(repository.Repository, bool) error))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWrapWithCloneFn_Execute_Call) Return(_a0 error) *MockWrapWithCloneFn_Execute_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWrapWithCloneFn_Execute_Call) RunAndReturn(run func(context.Context, repository.Repository, repository.CloneOptions, repository.PushOptions, func(repository.Repository, bool) error) error) *MockWrapWithCloneFn_Execute_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockWrapWithCloneFn creates a new instance of MockWrapWithCloneFn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
|
||||||
// The first argument is typically a *testing.T value.
|
|
||||||
func NewMockWrapWithCloneFn(t interface {
|
|
||||||
mock.TestingT
|
|
||||||
Cleanup(func())
|
|
||||||
}) *MockWrapWithCloneFn {
|
|
||||||
mock := &MockWrapWithCloneFn{}
|
|
||||||
mock.Mock.Test(t)
|
|
||||||
|
|
||||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
|
||||||
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
|
package export
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
|
||||||
|
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockWrapWithStageFn is an autogenerated mock type for the WrapWithStageFn type
|
||||||
|
type MockWrapWithStageFn struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockWrapWithStageFn_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *MockWrapWithStageFn) EXPECT() *MockWrapWithStageFn_Expecter {
|
||||||
|
return &MockWrapWithStageFn_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute provides a mock function with given fields: ctx, repo, stageOptions, fn
|
||||||
|
func (_m *MockWrapWithStageFn) Execute(ctx context.Context, repo repository.Repository, stageOptions repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
|
ret := _m.Called(ctx, repo, stageOptions, fn)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Execute")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, repository.Repository, repository.StageOptions, func(repository.Repository, bool) error) error); ok {
|
||||||
|
r0 = rf(ctx, repo, stageOptions, fn)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockWrapWithStageFn_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute'
|
||||||
|
type MockWrapWithStageFn_Execute_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - repo repository.Repository
|
||||||
|
// - stageOptions repository.StageOptions
|
||||||
|
// - fn func(repository.Repository , bool) error
|
||||||
|
func (_e *MockWrapWithStageFn_Expecter) Execute(ctx interface{}, repo interface{}, stageOptions interface{}, fn interface{}) *MockWrapWithStageFn_Execute_Call {
|
||||||
|
return &MockWrapWithStageFn_Execute_Call{Call: _e.mock.On("Execute", ctx, repo, stageOptions, fn)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockWrapWithStageFn_Execute_Call) Run(run func(ctx context.Context, repo repository.Repository, stageOptions repository.StageOptions, fn func(repository.Repository, bool) error)) *MockWrapWithStageFn_Execute_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(repository.Repository), args[2].(repository.StageOptions), args[3].(func(repository.Repository, bool) error))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockWrapWithStageFn_Execute_Call) Return(_a0 error) *MockWrapWithStageFn_Execute_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockWrapWithStageFn_Execute_Call) RunAndReturn(run func(context.Context, repository.Repository, repository.StageOptions, func(repository.Repository, bool) error) error) *MockWrapWithStageFn_Execute_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockWrapWithStageFn creates a new instance of MockWrapWithStageFn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
// The first argument is typically a *testing.T value.
|
||||||
|
func NewMockWrapWithStageFn(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *MockWrapWithStageFn {
|
||||||
|
mock := &MockWrapWithStageFn{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
@@ -67,7 +67,7 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := exportResource(ctx, options, client, shim, repositoryResources, progress); err != nil {
|
if err := exportResource(ctx, kind.Resource, options, client, shim, repositoryResources, progress); err != nil {
|
||||||
return fmt.Errorf("export %s: %w", kind.Resource, err)
|
return fmt.Errorf("export %s: %w", kind.Resource, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,6 +76,7 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func exportResource(ctx context.Context,
|
func exportResource(ctx context.Context,
|
||||||
|
resource string,
|
||||||
options provisioning.ExportJobOptions,
|
options provisioning.ExportJobOptions,
|
||||||
client dynamic.ResourceInterface,
|
client dynamic.ResourceInterface,
|
||||||
shim conversionShim,
|
shim conversionShim,
|
||||||
@@ -88,7 +89,7 @@ func exportResource(ctx context.Context,
|
|||||||
gvk := item.GroupVersionKind()
|
gvk := item.GroupVersionKind()
|
||||||
result := jobs.JobResourceResult{
|
result := jobs.JobResourceResult{
|
||||||
Name: item.GetName(),
|
Name: item.GetName(),
|
||||||
Resource: gvk.Kind,
|
Resource: resource,
|
||||||
Group: gvk.Group,
|
Group: gvk.Group,
|
||||||
Action: repository.FileActionCreated,
|
Action: repository.FileActionCreated,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,34 +9,33 @@ import (
|
|||||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
gogit "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/go-git"
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate mockery --name ExportFn --structname MockExportFn --inpackage --filename mock_export_fn.go --with-expecter
|
//go:generate mockery --name ExportFn --structname MockExportFn --inpackage --filename mock_export_fn.go --with-expecter
|
||||||
type ExportFn func(ctx context.Context, repoName string, options provisioning.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error
|
type ExportFn func(ctx context.Context, repoName string, options provisioning.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error
|
||||||
|
|
||||||
//go:generate mockery --name WrapWithCloneFn --structname MockWrapWithCloneFn --inpackage --filename mock_wrap_with_clone_fn.go --with-expecter
|
//go:generate mockery --name WrapWithStageFn --structname MockWrapWithStageFn --inpackage --filename mock_wrap_with_stage_fn.go --with-expecter
|
||||||
type WrapWithCloneFn func(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repo repository.Repository, cloned bool) error) error
|
type WrapWithStageFn func(ctx context.Context, repo repository.Repository, stageOptions repository.StageOptions, fn func(repo repository.Repository, staged bool) error) error
|
||||||
|
|
||||||
type ExportWorker struct {
|
type ExportWorker struct {
|
||||||
clientFactory resources.ClientFactory
|
clientFactory resources.ClientFactory
|
||||||
repositoryResources resources.RepositoryResourcesFactory
|
repositoryResources resources.RepositoryResourcesFactory
|
||||||
exportFn ExportFn
|
exportFn ExportFn
|
||||||
wrapWithCloneFn WrapWithCloneFn
|
wrapWithStageFn WrapWithStageFn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExportWorker(
|
func NewExportWorker(
|
||||||
clientFactory resources.ClientFactory,
|
clientFactory resources.ClientFactory,
|
||||||
repositoryResources resources.RepositoryResourcesFactory,
|
repositoryResources resources.RepositoryResourcesFactory,
|
||||||
exportFn ExportFn,
|
exportFn ExportFn,
|
||||||
wrapWithCloneFn WrapWithCloneFn,
|
wrapWithStageFn WrapWithStageFn,
|
||||||
) *ExportWorker {
|
) *ExportWorker {
|
||||||
return &ExportWorker{
|
return &ExportWorker{
|
||||||
clientFactory: clientFactory,
|
clientFactory: clientFactory,
|
||||||
repositoryResources: repositoryResources,
|
repositoryResources: repositoryResources,
|
||||||
exportFn: exportFn,
|
exportFn: exportFn,
|
||||||
wrapWithCloneFn: wrapWithCloneFn,
|
wrapWithStageFn: wrapWithStageFn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,32 +56,9 @@ func (r *ExportWorker) Process(ctx context.Context, repo repository.Repository,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
writer := gogit.Progress(func(line string) {
|
cloneOptions := repository.StageOptions{
|
||||||
progress.SetMessage(ctx, line)
|
|
||||||
}, "finished")
|
|
||||||
|
|
||||||
cloneOptions := repository.CloneOptions{
|
|
||||||
Timeout: 10 * time.Minute,
|
Timeout: 10 * time.Minute,
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
Progress: writer,
|
|
||||||
BeforeFn: func() error {
|
|
||||||
progress.SetMessage(ctx, "clone target")
|
|
||||||
// :( the branch is now baked into the repo
|
|
||||||
if options.Branch != "" {
|
|
||||||
return fmt.Errorf("branch is not supported for clonable repositories")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pushOptions := repository.PushOptions{
|
|
||||||
Timeout: 10 * time.Minute,
|
|
||||||
Progress: writer,
|
|
||||||
BeforeFn: func() error {
|
|
||||||
progress.SetMessage(ctx, "push changes")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := func(repo repository.Repository, _ bool) error {
|
fn := func(repo repository.Repository, _ bool) error {
|
||||||
@@ -104,5 +80,5 @@ func (r *ExportWorker) Process(ctx context.Context, repo repository.Repository,
|
|||||||
return r.exportFn(ctx, cfg.Name, *options, clients, repositoryResources, progress)
|
return r.exportFn(ctx, cfg.Name, *options, clients, repositoryResources, progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.wrapWithCloneFn(ctx, repo, cloneOptions, pushOptions, fn)
|
return r.wrapWithStageFn(ctx, repo, cloneOptions, fn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
mock "github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@@ -143,12 +142,12 @@ func TestExportWorker_ProcessFailedToCreateClients(t *testing.T) {
|
|||||||
mockClients := resources.NewMockClientFactory(t)
|
mockClients := resources.NewMockClientFactory(t)
|
||||||
|
|
||||||
mockClients.On("Clients", context.Background(), "test-namespace").Return(nil, errors.New("failed to create clients"))
|
mockClients.On("Clients", context.Background(), "test-namespace").Return(nil, errors.New("failed to create clients"))
|
||||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
mockStageFn := NewMockWrapWithStageFn(t)
|
||||||
mockCloneFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
mockStageFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return fn(repo, true)
|
return fn(repo, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
r := NewExportWorker(mockClients, nil, nil, mockCloneFn.Execute)
|
r := NewExportWorker(mockClients, nil, nil, mockStageFn.Execute)
|
||||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||||
|
|
||||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||||
@@ -179,12 +178,12 @@ func TestExportWorker_ProcessNotReaderWriter(t *testing.T) {
|
|||||||
mockClients.On("Clients", context.Background(), "test-namespace").Return(resourceClients, nil)
|
mockClients.On("Clients", context.Background(), "test-namespace").Return(resourceClients, nil)
|
||||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||||
|
|
||||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
mockStageFn := NewMockWrapWithStageFn(t)
|
||||||
mockCloneFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
mockStageFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return fn(repo, true)
|
return fn(repo, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
r := NewExportWorker(mockClients, nil, nil, mockCloneFn.Execute)
|
r := NewExportWorker(mockClients, nil, nil, mockStageFn.Execute)
|
||||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||||
require.EqualError(t, err, "export job submitted targeting repository that is not a ReaderWriter")
|
require.EqualError(t, err, "export job submitted targeting repository that is not a ReaderWriter")
|
||||||
}
|
}
|
||||||
@@ -216,16 +215,16 @@ func TestExportWorker_ProcessRepositoryResourcesError(t *testing.T) {
|
|||||||
mockRepoResources.On("Client", context.Background(), mockRepo).Return(nil, fmt.Errorf("failed to create repository resources client"))
|
mockRepoResources.On("Client", context.Background(), mockRepo).Return(nil, fmt.Errorf("failed to create repository resources client"))
|
||||||
|
|
||||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
mockStageFn := NewMockWrapWithStageFn(t)
|
||||||
mockCloneFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
mockStageFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return fn(repo, true)
|
return fn(repo, true)
|
||||||
})
|
})
|
||||||
r := NewExportWorker(mockClients, mockRepoResources, nil, mockCloneFn.Execute)
|
r := NewExportWorker(mockClients, mockRepoResources, nil, mockStageFn.Execute)
|
||||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||||
require.EqualError(t, err, "create repository resource client: failed to create repository resources client")
|
require.EqualError(t, err, "create repository resource client: failed to create repository resources client")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExportWorker_ProcessCloneAndPushOptions(t *testing.T) {
|
func TestExportWorker_ProcessStageOptions(t *testing.T) {
|
||||||
job := v0alpha1.Job{
|
job := v0alpha1.Job{
|
||||||
Spec: v0alpha1.JobSpec{
|
Spec: v0alpha1.JobSpec{
|
||||||
Action: v0alpha1.JobActionPush,
|
Action: v0alpha1.JobActionPush,
|
||||||
@@ -245,9 +244,7 @@ func TestExportWorker_ProcessCloneAndPushOptions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||||
// Verify progress messages are set
|
// No progress messages expected in current implementation
|
||||||
mockProgress.On("SetMessage", mock.Anything, "clone target").Return()
|
|
||||||
mockProgress.On("SetMessage", mock.Anything, "push changes").Return()
|
|
||||||
|
|
||||||
mockClients := resources.NewMockClientFactory(t)
|
mockClients := resources.NewMockClientFactory(t)
|
||||||
mockResourceClients := resources.NewMockResourceClients(t)
|
mockResourceClients := resources.NewMockResourceClients(t)
|
||||||
@@ -260,21 +257,15 @@ func TestExportWorker_ProcessCloneAndPushOptions(t *testing.T) {
|
|||||||
mockExportFn := NewMockExportFn(t)
|
mockExportFn := NewMockExportFn(t)
|
||||||
mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||||
|
|
||||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
mockStageFn := NewMockWrapWithStageFn(t)
|
||||||
// Verify clone and push options
|
// Verify clone and push options
|
||||||
mockCloneFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.CloneOptions) bool {
|
mockStageFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.StageOptions) bool {
|
||||||
return opts.Timeout == 10*time.Minute && !opts.PushOnWrites && opts.BeforeFn != nil
|
return opts.Timeout == 10*time.Minute && !opts.PushOnWrites
|
||||||
}), mock.MatchedBy(func(opts repository.PushOptions) bool {
|
}), mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return opts.Timeout == 10*time.Minute && opts.Progress != nil && opts.BeforeFn != nil
|
|
||||||
}), mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
|
||||||
// Execute both BeforeFn functions to verify progress messages
|
|
||||||
assert.NoError(t, cloneOpts.BeforeFn())
|
|
||||||
assert.NoError(t, pushOpts.BeforeFn())
|
|
||||||
|
|
||||||
return fn(repo, true)
|
return fn(repo, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockCloneFn.Execute)
|
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockStageFn.Execute)
|
||||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
@@ -310,17 +301,17 @@ func TestExportWorker_ProcessExportFnError(t *testing.T) {
|
|||||||
mockExportFn := NewMockExportFn(t)
|
mockExportFn := NewMockExportFn(t)
|
||||||
mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(errors.New("export failed"))
|
mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(errors.New("export failed"))
|
||||||
|
|
||||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
mockStageFn := NewMockWrapWithStageFn(t)
|
||||||
mockCloneFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
mockStageFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return fn(repo, true)
|
return fn(repo, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockCloneFn.Execute)
|
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockStageFn.Execute)
|
||||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||||
require.EqualError(t, err, "export failed")
|
require.EqualError(t, err, "export failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExportWorker_ProcessWrapWithCloneFnError(t *testing.T) {
|
func TestExportWorker_ProcessWrapWithStageFnError(t *testing.T) {
|
||||||
job := v0alpha1.Job{
|
job := v0alpha1.Job{
|
||||||
Spec: v0alpha1.JobSpec{
|
Spec: v0alpha1.JobSpec{
|
||||||
Action: v0alpha1.JobActionPush,
|
Action: v0alpha1.JobActionPush,
|
||||||
@@ -340,15 +331,15 @@ func TestExportWorker_ProcessWrapWithCloneFnError(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
mockStageFn := NewMockWrapWithStageFn(t)
|
||||||
mockCloneFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(errors.New("clone failed"))
|
mockStageFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything).Return(errors.New("stage failed"))
|
||||||
|
|
||||||
r := NewExportWorker(nil, nil, nil, mockCloneFn.Execute)
|
r := NewExportWorker(nil, nil, nil, mockStageFn.Execute)
|
||||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||||
require.EqualError(t, err, "clone failed")
|
require.EqualError(t, err, "stage failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExportWorker_ProcessBranchNotAllowedForClonableRepositories(t *testing.T) {
|
func TestExportWorker_ProcessBranchNotAllowedForStageableRepositories(t *testing.T) {
|
||||||
job := v0alpha1.Job{
|
job := v0alpha1.Job{
|
||||||
Spec: v0alpha1.JobSpec{
|
Spec: v0alpha1.JobSpec{
|
||||||
Action: v0alpha1.JobActionPush,
|
Action: v0alpha1.JobActionPush,
|
||||||
@@ -362,24 +353,16 @@ func TestExportWorker_ProcessBranchNotAllowedForClonableRepositories(t *testing.
|
|||||||
mockRepo.On("Config").Return(&v0alpha1.Repository{
|
mockRepo.On("Config").Return(&v0alpha1.Repository{
|
||||||
Spec: v0alpha1.RepositorySpec{
|
Spec: v0alpha1.RepositorySpec{
|
||||||
Type: v0alpha1.GitHubRepositoryType,
|
Type: v0alpha1.GitHubRepositoryType,
|
||||||
Workflows: []v0alpha1.Workflow{v0alpha1.BranchWorkflow},
|
Workflows: []v0alpha1.Workflow{v0alpha1.WriteWorkflow}, // Only write workflow, not branch
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||||
mockProgress.On("SetMessage", mock.Anything, "clone target").Return()
|
// No progress messages expected in current implementation
|
||||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
|
||||||
mockCloneFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
|
||||||
if cloneOpts.BeforeFn != nil {
|
|
||||||
return cloneOpts.BeforeFn()
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn(repo, true)
|
r := NewExportWorker(nil, nil, nil, nil)
|
||||||
})
|
|
||||||
|
|
||||||
r := NewExportWorker(nil, nil, nil, mockCloneFn.Execute)
|
|
||||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||||
require.EqualError(t, err, "branch is not supported for clonable repositories")
|
require.EqualError(t, err, "this repository does not support the branch workflow")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExportWorker_ProcessGitRepository(t *testing.T) {
|
func TestExportWorker_ProcessGitRepository(t *testing.T) {
|
||||||
@@ -407,9 +390,7 @@ func TestExportWorker_ProcessGitRepository(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
mockProgress := jobs.NewMockJobProgressRecorder(t)
|
||||||
// Verify progress messages are set
|
// No progress messages expected in current implementation
|
||||||
mockProgress.On("SetMessage", mock.Anything, "clone target").Return()
|
|
||||||
mockProgress.On("SetMessage", mock.Anything, "push changes").Return()
|
|
||||||
|
|
||||||
mockClients := resources.NewMockClientFactory(t)
|
mockClients := resources.NewMockClientFactory(t)
|
||||||
mockResourceClients := resources.NewMockResourceClients(t)
|
mockResourceClients := resources.NewMockResourceClients(t)
|
||||||
@@ -422,21 +403,15 @@ func TestExportWorker_ProcessGitRepository(t *testing.T) {
|
|||||||
mockExportFn := NewMockExportFn(t)
|
mockExportFn := NewMockExportFn(t)
|
||||||
mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||||
|
|
||||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
mockStageFn := NewMockWrapWithStageFn(t)
|
||||||
// Verify clone and push options
|
// Verify clone and push options
|
||||||
mockCloneFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.CloneOptions) bool {
|
mockStageFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.StageOptions) bool {
|
||||||
return opts.Timeout == 10*time.Minute && !opts.PushOnWrites && opts.BeforeFn != nil
|
return opts.Timeout == 10*time.Minute && !opts.PushOnWrites
|
||||||
}), mock.MatchedBy(func(opts repository.PushOptions) bool {
|
}), mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return opts.Timeout == 10*time.Minute && opts.Progress != nil && opts.BeforeFn != nil
|
|
||||||
}), mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
|
||||||
// Execute both BeforeFn functions to verify progress messages
|
|
||||||
assert.NoError(t, cloneOpts.BeforeFn())
|
|
||||||
assert.NoError(t, pushOpts.BeforeFn())
|
|
||||||
|
|
||||||
return fn(repo, true)
|
return fn(repo, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockCloneFn.Execute)
|
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockStageFn.Execute)
|
||||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
@@ -477,12 +452,12 @@ func TestExportWorker_ProcessGitRepositoryExportFnError(t *testing.T) {
|
|||||||
mockExportFn := NewMockExportFn(t)
|
mockExportFn := NewMockExportFn(t)
|
||||||
mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(errors.New("export failed"))
|
mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(errors.New("export failed"))
|
||||||
|
|
||||||
mockCloneFn := NewMockWrapWithCloneFn(t)
|
mockStageFn := NewMockWrapWithStageFn(t)
|
||||||
mockCloneFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
mockStageFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return fn(repo, true)
|
return fn(repo, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockCloneFn.Execute)
|
r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockStageFn.Execute)
|
||||||
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
err := r.Process(context.Background(), mockRepo, job, mockProgress)
|
||||||
require.EqualError(t, err, "export failed")
|
require.EqualError(t, err, "export failed")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,57 +10,38 @@ import (
|
|||||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
gogit "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/go-git"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LegacyMigrator struct {
|
type LegacyMigrator struct {
|
||||||
legacyMigrator LegacyResourcesMigrator
|
legacyMigrator LegacyResourcesMigrator
|
||||||
storageSwapper StorageSwapper
|
storageSwapper StorageSwapper
|
||||||
syncWorker jobs.Worker
|
syncWorker jobs.Worker
|
||||||
wrapWithCloneFn WrapWithCloneFn
|
wrapWithStageFn WrapWithStageFn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLegacyMigrator(
|
func NewLegacyMigrator(
|
||||||
legacyMigrator LegacyResourcesMigrator,
|
legacyMigrator LegacyResourcesMigrator,
|
||||||
storageSwapper StorageSwapper,
|
storageSwapper StorageSwapper,
|
||||||
syncWorker jobs.Worker,
|
syncWorker jobs.Worker,
|
||||||
wrapWithCloneFn WrapWithCloneFn,
|
wrapWithStageFn WrapWithStageFn,
|
||||||
) *LegacyMigrator {
|
) *LegacyMigrator {
|
||||||
return &LegacyMigrator{
|
return &LegacyMigrator{
|
||||||
legacyMigrator: legacyMigrator,
|
legacyMigrator: legacyMigrator,
|
||||||
storageSwapper: storageSwapper,
|
storageSwapper: storageSwapper,
|
||||||
syncWorker: syncWorker,
|
syncWorker: syncWorker,
|
||||||
wrapWithCloneFn: wrapWithCloneFn,
|
wrapWithStageFn: wrapWithStageFn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *LegacyMigrator) Migrate(ctx context.Context, rw repository.ReaderWriter, options provisioning.MigrateJobOptions, progress jobs.JobProgressRecorder) error {
|
func (m *LegacyMigrator) Migrate(ctx context.Context, rw repository.ReaderWriter, options provisioning.MigrateJobOptions, progress jobs.JobProgressRecorder) error {
|
||||||
namespace := rw.Config().Namespace
|
namespace := rw.Config().Namespace
|
||||||
|
stageOptions := repository.StageOptions{
|
||||||
writer := gogit.Progress(func(line string) {
|
|
||||||
progress.SetMessage(ctx, line)
|
|
||||||
}, "finished")
|
|
||||||
cloneOptions := repository.CloneOptions{
|
|
||||||
PushOnWrites: options.History,
|
PushOnWrites: options.History,
|
||||||
// TODO: make this configurable
|
// TODO: make this configurable
|
||||||
Timeout: 10 * time.Minute,
|
Timeout: 10 * time.Minute,
|
||||||
Progress: writer,
|
|
||||||
BeforeFn: func() error {
|
|
||||||
progress.SetMessage(ctx, "clone repository")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pushOptions := repository.PushOptions{
|
|
||||||
// TODO: make this configurable
|
|
||||||
Timeout: 10 * time.Minute,
|
|
||||||
Progress: writer,
|
|
||||||
BeforeFn: func() error {
|
|
||||||
progress.SetMessage(ctx, "push changes")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.wrapWithCloneFn(ctx, rw, cloneOptions, pushOptions, func(repo repository.Repository, cloned bool) error {
|
if err := m.wrapWithStageFn(ctx, rw, stageOptions, func(repo repository.Repository, staged bool) error {
|
||||||
rw, ok := repo.(repository.ReaderWriter)
|
rw, ok := repo.(repository.ReaderWriter)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("migration job submitted targeting repository that is not a ReaderWriter")
|
return errors.New("migration job submitted targeting repository that is not a ReaderWriter")
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package migrate
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -16,12 +15,12 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWrapWithCloneFn(t *testing.T) {
|
func TestWrapWithStageFn(t *testing.T) {
|
||||||
t.Run("should return error when repository is not a ReaderWriter", func(t *testing.T) {
|
t.Run("should return error when repository is not a ReaderWriter", func(t *testing.T) {
|
||||||
// Setup
|
// Setup
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
// Create the wrapper function that matches WrapWithCloneFn signature
|
// Create the wrapper function that matches WrapWithCloneFn signature
|
||||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
// pass a reader to function call
|
// pass a reader to function call
|
||||||
repo := repository.NewMockReader(t)
|
repo := repository.NewMockReader(t)
|
||||||
return fn(repo, true)
|
return fn(repo, true)
|
||||||
@@ -56,7 +55,7 @@ func TestWrapWithCloneFn_Error(t *testing.T) {
|
|||||||
expectedErr := errors.New("clone failed")
|
expectedErr := errors.New("clone failed")
|
||||||
|
|
||||||
// Create the wrapper function that returns an error
|
// Create the wrapper function that returns an error
|
||||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return expectedErr
|
return expectedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +97,7 @@ func TestLegacyMigrator_MigrateFails(t *testing.T) {
|
|||||||
mockWorker := jobs.NewMockWorker(t)
|
mockWorker := jobs.NewMockWorker(t)
|
||||||
|
|
||||||
// Create a wrapper function that calls the provided function
|
// Create a wrapper function that calls the provided function
|
||||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return fn(rw, true)
|
return fn(rw, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +146,7 @@ func TestLegacyMigrator_ResetUnifiedStorageFails(t *testing.T) {
|
|||||||
mockWorker := jobs.NewMockWorker(t)
|
mockWorker := jobs.NewMockWorker(t)
|
||||||
|
|
||||||
// Create a wrapper function that calls the provided function
|
// Create a wrapper function that calls the provided function
|
||||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return fn(rw, true)
|
return fn(rw, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +201,7 @@ func TestLegacyMigrator_SyncFails(t *testing.T) {
|
|||||||
}), mock.Anything).Return(expectedErr)
|
}), mock.Anything).Return(expectedErr)
|
||||||
|
|
||||||
// Create a wrapper function that calls the provided function
|
// Create a wrapper function that calls the provided function
|
||||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return fn(rw, true)
|
return fn(rw, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +256,7 @@ func TestLegacyMigrator_SyncFails(t *testing.T) {
|
|||||||
}), mock.Anything).Return(syncErr)
|
}), mock.Anything).Return(syncErr)
|
||||||
|
|
||||||
// Create a wrapper function that calls the provided function
|
// Create a wrapper function that calls the provided function
|
||||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return fn(rw, true)
|
return fn(rw, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +309,7 @@ func TestLegacyMigrator_Success(t *testing.T) {
|
|||||||
}), mock.Anything).Return(nil)
|
}), mock.Anything).Return(nil)
|
||||||
|
|
||||||
// Create a wrapper function that calls the provided function
|
// Create a wrapper function that calls the provided function
|
||||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
return fn(rw, true)
|
return fn(rw, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,19 +351,7 @@ func TestLegacyMigrator_BeforeFnExecution(t *testing.T) {
|
|||||||
mockStorageSwapper := NewMockStorageSwapper(t)
|
mockStorageSwapper := NewMockStorageSwapper(t)
|
||||||
mockWorker := jobs.NewMockWorker(t)
|
mockWorker := jobs.NewMockWorker(t)
|
||||||
// Create a wrapper function that calls the provided function
|
// Create a wrapper function that calls the provided function
|
||||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
if clone.BeforeFn != nil {
|
|
||||||
if err := clone.BeforeFn(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if push.BeforeFn != nil {
|
|
||||||
if err := push.BeforeFn(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New("abort test here")
|
return errors.New("abort test here")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,8 +363,7 @@ func TestLegacyMigrator_BeforeFnExecution(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
progress := jobs.NewMockJobProgressRecorder(t)
|
progress := jobs.NewMockJobProgressRecorder(t)
|
||||||
progress.On("SetMessage", mock.Anything, "clone repository").Return()
|
// No progress messages expected in current staging implementation
|
||||||
progress.On("SetMessage", mock.Anything, "push changes").Return()
|
|
||||||
|
|
||||||
// Execute
|
// Execute
|
||||||
repo := repository.NewMockRepository(t)
|
repo := repository.NewMockRepository(t)
|
||||||
@@ -399,19 +385,7 @@ func TestLegacyMigrator_ProgressScanner(t *testing.T) {
|
|||||||
mockWorker := jobs.NewMockWorker(t)
|
mockWorker := jobs.NewMockWorker(t)
|
||||||
|
|
||||||
// Create a wrapper function that calls the provided function
|
// Create a wrapper function that calls the provided function
|
||||||
wrapFn := func(ctx context.Context, rw repository.Repository, clone repository.CloneOptions, push repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
wrapFn := func(ctx context.Context, rw repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error {
|
||||||
if clone.Progress != nil {
|
|
||||||
if _, err := clone.Progress.Write([]byte("clone repository\n")); err != nil {
|
|
||||||
return fmt.Errorf("failed to write to clone progress in tests: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if push.Progress != nil {
|
|
||||||
if _, err := push.Progress.Write([]byte("push changes\n")); err != nil {
|
|
||||||
return fmt.Errorf("failed to write to push progress in tests: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New("abort test here")
|
return errors.New("abort test here")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,8 +397,7 @@ func TestLegacyMigrator_ProgressScanner(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
progress := jobs.NewMockJobProgressRecorder(t)
|
progress := jobs.NewMockJobProgressRecorder(t)
|
||||||
progress.On("SetMessage", mock.Anything, "clone repository").Return()
|
// No progress messages expected in current staging implementation
|
||||||
progress.On("SetMessage", mock.Anything, "push changes").Return()
|
|
||||||
|
|
||||||
repo := repository.NewMockRepository(t)
|
repo := repository.NewMockRepository(t)
|
||||||
repo.On("Config").Return(&provisioning.Repository{
|
repo.On("Config").Return(&provisioning.Repository{
|
||||||
@@ -437,10 +410,7 @@ func TestLegacyMigrator_ProgressScanner(t *testing.T) {
|
|||||||
require.EqualError(t, err, "migrate from SQL: abort test here")
|
require.EqualError(t, err, "migrate from SQL: abort test here")
|
||||||
|
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
if len(progress.Calls) != 2 {
|
// No progress message calls expected in current staging implementation
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return progress.AssertExpectations(t)
|
return progress.AssertExpectations(t)
|
||||||
}, time.Second, 10*time.Millisecond)
|
}, time.Second, 10*time.Millisecond)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
package migrate
|
package migrate
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
package migrate
|
package migrate
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
package migrate
|
package migrate
|
||||||
|
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
|
||||||
|
|
||||||
package migrate
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
|
|
||||||
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockWrapWithCloneFn is an autogenerated mock type for the WrapWithCloneFn type
|
|
||||||
type MockWrapWithCloneFn struct {
|
|
||||||
mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockWrapWithCloneFn_Expecter struct {
|
|
||||||
mock *mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_m *MockWrapWithCloneFn) EXPECT() *MockWrapWithCloneFn_Expecter {
|
|
||||||
return &MockWrapWithCloneFn_Expecter{mock: &_m.Mock}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute provides a mock function with given fields: ctx, repo, cloneOptions, pushOptions, fn
|
|
||||||
func (_m *MockWrapWithCloneFn) Execute(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repository.Repository, bool) error) error {
|
|
||||||
ret := _m.Called(ctx, repo, cloneOptions, pushOptions, fn)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for Execute")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, repository.Repository, repository.CloneOptions, repository.PushOptions, func(repository.Repository, bool) error) error); ok {
|
|
||||||
r0 = rf(ctx, repo, cloneOptions, pushOptions, fn)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockWrapWithCloneFn_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute'
|
|
||||||
type MockWrapWithCloneFn_Execute_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - repo repository.Repository
|
|
||||||
// - cloneOptions repository.CloneOptions
|
|
||||||
// - pushOptions repository.PushOptions
|
|
||||||
// - fn func(repository.Repository , bool) error
|
|
||||||
func (_e *MockWrapWithCloneFn_Expecter) Execute(ctx interface{}, repo interface{}, cloneOptions interface{}, pushOptions interface{}, fn interface{}) *MockWrapWithCloneFn_Execute_Call {
|
|
||||||
return &MockWrapWithCloneFn_Execute_Call{Call: _e.mock.On("Execute", ctx, repo, cloneOptions, pushOptions, fn)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWrapWithCloneFn_Execute_Call) Run(run func(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repository.Repository, bool) error)) *MockWrapWithCloneFn_Execute_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(repository.Repository), args[2].(repository.CloneOptions), args[3].(repository.PushOptions), args[4].(func(repository.Repository, bool) error))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWrapWithCloneFn_Execute_Call) Return(_a0 error) *MockWrapWithCloneFn_Execute_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWrapWithCloneFn_Execute_Call) RunAndReturn(run func(context.Context, repository.Repository, repository.CloneOptions, repository.PushOptions, func(repository.Repository, bool) error) error) *MockWrapWithCloneFn_Execute_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockWrapWithCloneFn creates a new instance of MockWrapWithCloneFn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
|
||||||
// The first argument is typically a *testing.T value.
|
|
||||||
func NewMockWrapWithCloneFn(t interface {
|
|
||||||
mock.TestingT
|
|
||||||
Cleanup(func())
|
|
||||||
}) *MockWrapWithCloneFn {
|
|
||||||
mock := &MockWrapWithCloneFn{}
|
|
||||||
mock.Mock.Test(t)
|
|
||||||
|
|
||||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
|
||||||
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate mockery --name WrapWithCloneFn --structname MockWrapWithCloneFn --inpackage --filename mock_wrap_with_clone_fn.go --with-expecter
|
//go:generate mockery --name WrapWithStageFn --structname MockWrapWithStageFn --inpackage --filename mock_wrap_with_stage_fn.go --with-expecter
|
||||||
type WrapWithCloneFn func(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repo repository.Repository, cloned bool) error) error
|
type WrapWithStageFn func(ctx context.Context, repo repository.Repository, stageOptions repository.StageOptions, fn func(repo repository.Repository, staged bool) error) error
|
||||||
|
|
||||||
type UnifiedStorageMigrator struct {
|
type UnifiedStorageMigrator struct {
|
||||||
namespaceCleaner NamespaceCleaner
|
namespaceCleaner NamespaceCleaner
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/local"
|
||||||
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
|
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ func TestMigrationWorker_WithHistory(t *testing.T) {
|
|||||||
progressRecorder.On("SetTotal", mock.Anything, 10).Return()
|
progressRecorder.On("SetTotal", mock.Anything, 10).Return()
|
||||||
progressRecorder.On("Strict").Return()
|
progressRecorder.On("Strict").Return()
|
||||||
|
|
||||||
repo := repository.NewLocal(&provisioning.Repository{}, nil)
|
repo := local.NewLocal(&provisioning.Repository{}, nil)
|
||||||
err := worker.Process(context.Background(), repo, job, progressRecorder)
|
err := worker.Process(context.Background(), repo, job, progressRecorder)
|
||||||
require.EqualError(t, err, "history is only supported for github repositories")
|
require.EqualError(t, err, "history is only supported for github repositories")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package sync
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||||
@@ -143,17 +142,7 @@ func Changes(source []repository.FileTreeEntry, target *provisioning.ResourceLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deepest first (stable sort order)
|
// Deepest first (stable sort order)
|
||||||
sort.Slice(changes, func(i, j int) bool {
|
safepath.SortByDepth(changes, func(c ResourceFileChange) string { return c.Path }, false)
|
||||||
if safepath.Depth(changes[i].Path) > safepath.Depth(changes[j].Path) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if safepath.Depth(changes[i].Path) < safepath.Depth(changes[j].Path) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return changes[i].Path < changes[j].Path
|
|
||||||
})
|
|
||||||
|
|
||||||
return changes, nil
|
return changes, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/migrate"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/migrate"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/sync"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/sync"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/git"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
"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/local"
|
||||||
"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"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources/signature"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources/signature"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||||
@@ -76,7 +76,7 @@ type APIBuilder struct {
|
|||||||
features featuremgmt.FeatureToggles
|
features featuremgmt.FeatureToggles
|
||||||
|
|
||||||
getter rest.Getter
|
getter rest.Getter
|
||||||
localFileResolver *repository.LocalFolderResolver
|
localFileResolver *local.LocalFolderResolver
|
||||||
parsers resources.ParserFactory
|
parsers resources.ParserFactory
|
||||||
repositoryResources resources.RepositoryResourcesFactory
|
repositoryResources resources.RepositoryResourcesFactory
|
||||||
clients resources.ClientFactory
|
clients resources.ClientFactory
|
||||||
@@ -105,7 +105,7 @@ type APIBuilder struct {
|
|||||||
// It avoids anything that is core to Grafana, such that it can be used in a multi-tenant service down the line.
|
// It avoids anything that is core to Grafana, such that it can be used in a multi-tenant service down the line.
|
||||||
// This means there are no hidden dependencies, and no use of e.g. *settings.Cfg.
|
// This means there are no hidden dependencies, and no use of e.g. *settings.Cfg.
|
||||||
func NewAPIBuilder(
|
func NewAPIBuilder(
|
||||||
local *repository.LocalFolderResolver,
|
local *local.LocalFolderResolver,
|
||||||
features featuremgmt.FeatureToggles,
|
features featuremgmt.FeatureToggles,
|
||||||
unified resource.ResourceClient,
|
unified resource.ResourceClient,
|
||||||
clonedir string, // where repo clones are managed
|
clonedir string, // where repo clones are managed
|
||||||
@@ -168,14 +168,7 @@ func RegisterAPIService(
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := logging.DefaultLogger.With("logger", "provisioning startup")
|
folderResolver := &local.LocalFolderResolver{
|
||||||
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,
|
PermittedPrefixes: cfg.PermittedProvisioningPaths,
|
||||||
HomePath: safepath.Clean(cfg.HomePath),
|
HomePath: safepath.Clean(cfg.HomePath),
|
||||||
}
|
}
|
||||||
@@ -606,11 +599,12 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH
|
|||||||
|
|
||||||
b.repositoryLister = repoInformer.Lister()
|
b.repositoryLister = repoInformer.Lister()
|
||||||
|
|
||||||
|
stageIfPossible := repository.WrapWithStageAndPushIfPossible
|
||||||
exportWorker := export.NewExportWorker(
|
exportWorker := export.NewExportWorker(
|
||||||
b.clients,
|
b.clients,
|
||||||
b.repositoryResources,
|
b.repositoryResources,
|
||||||
export.ExportAll,
|
export.ExportAll,
|
||||||
repository.WrapWithCloneAndPushIfPossible,
|
stageIfPossible,
|
||||||
)
|
)
|
||||||
|
|
||||||
b.statusPatcher = controller.NewRepositoryStatusPatcher(b.GetClient())
|
b.statusPatcher = controller.NewRepositoryStatusPatcher(b.GetClient())
|
||||||
@@ -636,7 +630,7 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH
|
|||||||
legacyResources,
|
legacyResources,
|
||||||
storageSwapper,
|
storageSwapper,
|
||||||
syncWorker,
|
syncWorker,
|
||||||
repository.WrapWithCloneAndPushIfPossible,
|
stageIfPossible,
|
||||||
)
|
)
|
||||||
|
|
||||||
cleaner := migrate.NewNamespaceCleaner(b.clients)
|
cleaner := migrate.NewNamespaceCleaner(b.clients)
|
||||||
@@ -1170,52 +1164,63 @@ func (b *APIBuilder) AsRepository(ctx context.Context, r *provisioning.Repositor
|
|||||||
|
|
||||||
switch r.Spec.Type {
|
switch r.Spec.Type {
|
||||||
case provisioning.LocalRepositoryType:
|
case provisioning.LocalRepositoryType:
|
||||||
return repository.NewLocal(r, b.localFileResolver), nil
|
return local.NewLocal(r, b.localFileResolver), nil
|
||||||
case provisioning.GitRepositoryType:
|
case provisioning.GitRepositoryType:
|
||||||
return nanogit.NewGitRepository(ctx, b.secrets, r, nanogit.RepositoryConfig{
|
// Decrypt token if needed
|
||||||
|
token := r.Spec.Git.Token
|
||||||
|
if token == "" {
|
||||||
|
decrypted, err := b.secrets.Decrypt(ctx, r.Spec.Git.EncryptedToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decrypt git token: %w", err)
|
||||||
|
}
|
||||||
|
token = string(decrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
return git.NewGitRepository(ctx, r, git.RepositoryConfig{
|
||||||
URL: r.Spec.Git.URL,
|
URL: r.Spec.Git.URL,
|
||||||
Branch: r.Spec.Git.Branch,
|
Branch: r.Spec.Git.Branch,
|
||||||
Path: r.Spec.Git.Path,
|
Path: r.Spec.Git.Path,
|
||||||
Token: r.Spec.Git.Token,
|
Token: token,
|
||||||
EncryptedToken: r.Spec.Git.EncryptedToken,
|
EncryptedToken: r.Spec.Git.EncryptedToken,
|
||||||
})
|
})
|
||||||
case provisioning.GitHubRepositoryType:
|
case provisioning.GitHubRepositoryType:
|
||||||
cloneFn := func(ctx context.Context, opts repository.CloneOptions) (repository.ClonedRepository, error) {
|
|
||||||
return gogit.Clone(ctx, b.clonedir, r, opts, b.secrets)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiRepo, err := repository.NewGitHub(ctx, r, b.ghFactory, b.secrets, cloneFn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create github API repository: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := logging.FromContext(ctx).With("url", r.Spec.GitHub.URL, "branch", r.Spec.GitHub.Branch, "path", r.Spec.GitHub.Path)
|
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.Info("Instantiating Github repository")
|
||||||
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
|
ghCfg := r.Spec.GitHub
|
||||||
if ghCfg == nil {
|
if ghCfg == nil {
|
||||||
return nil, fmt.Errorf("github configuration is required for nano git")
|
return nil, fmt.Errorf("github configuration is required for nano git")
|
||||||
}
|
}
|
||||||
|
|
||||||
gitCfg := nanogit.RepositoryConfig{
|
// Decrypt GitHub token if needed
|
||||||
|
ghToken := ghCfg.Token
|
||||||
|
if ghToken == "" && len(ghCfg.EncryptedToken) > 0 {
|
||||||
|
decrypted, err := b.secrets.Decrypt(ctx, ghCfg.EncryptedToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decrypt github token: %w", err)
|
||||||
|
}
|
||||||
|
ghToken = string(decrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
gitCfg := git.RepositoryConfig{
|
||||||
URL: ghCfg.URL,
|
URL: ghCfg.URL,
|
||||||
Branch: ghCfg.Branch,
|
Branch: ghCfg.Branch,
|
||||||
Path: ghCfg.Path,
|
Path: ghCfg.Path,
|
||||||
Token: ghCfg.Token,
|
Token: ghToken,
|
||||||
EncryptedToken: ghCfg.EncryptedToken,
|
EncryptedToken: ghCfg.EncryptedToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
nanogitRepo, err := nanogit.NewGitRepository(ctx, b.secrets, r, gitCfg)
|
gitRepo, err := git.NewGitRepository(ctx, r, gitCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating nanogit repository: %w", err)
|
return nil, fmt.Errorf("error creating git repository: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nanogit.NewGithubRepository(apiRepo, nanogitRepo), nil
|
ghRepo, err := github.NewGitHub(ctx, r, gitRepo, b.ghFactory, ghToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating github repository: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ghRepo, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown repository type (%s)", r.Spec.Type)
|
return nil, fmt.Errorf("unknown repository type (%s)", r.Spec.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
|
||||||
|
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockClonableRepository is an autogenerated mock type for the ClonableRepository type
|
|
||||||
type MockClonableRepository struct {
|
|
||||||
mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockClonableRepository_Expecter struct {
|
|
||||||
mock *mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_m *MockClonableRepository) EXPECT() *MockClonableRepository_Expecter {
|
|
||||||
return &MockClonableRepository_Expecter{mock: &_m.Mock}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone provides a mock function with given fields: ctx, opts
|
|
||||||
func (_m *MockClonableRepository) Clone(ctx context.Context, opts CloneOptions) (ClonedRepository, error) {
|
|
||||||
ret := _m.Called(ctx, opts)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for Clone")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 ClonedRepository
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) (ClonedRepository, error)); ok {
|
|
||||||
return rf(ctx, opts)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) ClonedRepository); ok {
|
|
||||||
r0 = rf(ctx, opts)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).(ClonedRepository)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, CloneOptions) error); ok {
|
|
||||||
r1 = rf(ctx, opts)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClonableRepository_Clone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clone'
|
|
||||||
type MockClonableRepository_Clone_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - opts CloneOptions
|
|
||||||
func (_e *MockClonableRepository_Expecter) Clone(ctx interface{}, opts interface{}) *MockClonableRepository_Clone_Call {
|
|
||||||
return &MockClonableRepository_Clone_Call{Call: _e.mock.On("Clone", ctx, opts)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClonableRepository_Clone_Call) Run(run func(ctx context.Context, opts CloneOptions)) *MockClonableRepository_Clone_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(CloneOptions))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClonableRepository_Clone_Call) Return(_a0 ClonedRepository, _a1 error) *MockClonableRepository_Clone_Call {
|
|
||||||
_c.Call.Return(_a0, _a1)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClonableRepository_Clone_Call) RunAndReturn(run func(context.Context, CloneOptions) (ClonedRepository, error)) *MockClonableRepository_Clone_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockClonableRepository creates a new instance of MockClonableRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
|
||||||
// The first argument is typically a *testing.T value.
|
|
||||||
func NewMockClonableRepository(t interface {
|
|
||||||
mock.TestingT
|
|
||||||
Cleanup(func())
|
|
||||||
}) *MockClonableRepository {
|
|
||||||
mock := &MockClonableRepository{}
|
|
||||||
mock.Mock.Test(t)
|
|
||||||
|
|
||||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
|
||||||
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WrapWithCloneAndPushIfPossible clones a repository if possible, executes operations on the clone,
|
|
||||||
// and automatically pushes changes when the function completes. For repositories that support cloning,
|
|
||||||
// all operations are transparently executed on the clone, and the clone is automatically cleaned up
|
|
||||||
// afterward. If cloning is not supported, the original repository instance is used directly.
|
|
||||||
func WrapWithCloneAndPushIfPossible(
|
|
||||||
ctx context.Context,
|
|
||||||
repo Repository,
|
|
||||||
cloneOptions CloneOptions,
|
|
||||||
pushOptions PushOptions,
|
|
||||||
fn func(repo Repository, cloned bool) error,
|
|
||||||
) error {
|
|
||||||
clonable, ok := repo.(ClonableRepository)
|
|
||||||
if !ok {
|
|
||||||
return fn(repo, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
clone, err := clonable.Clone(ctx, cloneOptions)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("clone repository: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't, we simply log it
|
|
||||||
// FIXME: should we handle this differently?
|
|
||||||
defer func() {
|
|
||||||
if err := clone.Remove(ctx); err != nil {
|
|
||||||
logging.FromContext(ctx).Error("failed to remove cloned repository after export", "err", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := fn(clone, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return clone.Push(ctx, pushOptions)
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
|
||||||
|
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockCloneFn is an autogenerated mock type for the CloneFn type
|
|
||||||
type MockCloneFn struct {
|
|
||||||
mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockCloneFn_Expecter struct {
|
|
||||||
mock *mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_m *MockCloneFn) EXPECT() *MockCloneFn_Expecter {
|
|
||||||
return &MockCloneFn_Expecter{mock: &_m.Mock}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute provides a mock function with given fields: ctx, opts
|
|
||||||
func (_m *MockCloneFn) Execute(ctx context.Context, opts CloneOptions) (ClonedRepository, error) {
|
|
||||||
ret := _m.Called(ctx, opts)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for Execute")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 ClonedRepository
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) (ClonedRepository, error)); ok {
|
|
||||||
return rf(ctx, opts)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) ClonedRepository); ok {
|
|
||||||
r0 = rf(ctx, opts)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).(ClonedRepository)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, CloneOptions) error); ok {
|
|
||||||
r1 = rf(ctx, opts)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockCloneFn_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute'
|
|
||||||
type MockCloneFn_Execute_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - opts CloneOptions
|
|
||||||
func (_e *MockCloneFn_Expecter) Execute(ctx interface{}, opts interface{}) *MockCloneFn_Execute_Call {
|
|
||||||
return &MockCloneFn_Execute_Call{Call: _e.mock.On("Execute", ctx, opts)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockCloneFn_Execute_Call) Run(run func(ctx context.Context, opts CloneOptions)) *MockCloneFn_Execute_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(CloneOptions))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockCloneFn_Execute_Call) Return(_a0 ClonedRepository, _a1 error) *MockCloneFn_Execute_Call {
|
|
||||||
_c.Call.Return(_a0, _a1)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockCloneFn_Execute_Call) RunAndReturn(run func(context.Context, CloneOptions) (ClonedRepository, error)) *MockCloneFn_Execute_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockCloneFn creates a new instance of MockCloneFn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
|
||||||
// The first argument is typically a *testing.T value.
|
|
||||||
func NewMockCloneFn(t interface {
|
|
||||||
mock.TestingT
|
|
||||||
Cleanup(func())
|
|
||||||
}) *MockCloneFn {
|
|
||||||
mock := &MockCloneFn{}
|
|
||||||
mock.Mock.Test(t)
|
|
||||||
|
|
||||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
|
||||||
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/mock"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mockClonableRepo struct {
|
|
||||||
*MockClonableRepository
|
|
||||||
*MockClonedRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_WrapWithCloneAndPushIfPossible_NonClonableRepository(t *testing.T) {
|
|
||||||
nonClonable := NewMockRepository(t)
|
|
||||||
var called bool
|
|
||||||
fn := func(repo Repository, cloned bool) error {
|
|
||||||
called = true
|
|
||||||
return errors.New("operation failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := WrapWithCloneAndPushIfPossible(context.Background(), nonClonable, CloneOptions{}, PushOptions{}, fn)
|
|
||||||
require.EqualError(t, err, "operation failed")
|
|
||||||
require.True(t, called)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrapWithCloneAndPushIfPossible(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
setupMocks func(t *testing.T) *mockClonableRepo
|
|
||||||
operation func(repo Repository, cloned bool) error
|
|
||||||
expectedError string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "successful clone, operation, and push",
|
|
||||||
setupMocks: func(t *testing.T) *mockClonableRepo {
|
|
||||||
mockRepo := NewMockClonableRepository(t)
|
|
||||||
mockCloned := NewMockClonedRepository(t)
|
|
||||||
|
|
||||||
mockRepo.EXPECT().Clone(mock.Anything, CloneOptions{}).Return(mockCloned, nil)
|
|
||||||
mockCloned.EXPECT().Push(mock.Anything, PushOptions{}).Return(nil)
|
|
||||||
mockCloned.EXPECT().Remove(mock.Anything).Return(nil)
|
|
||||||
return &mockClonableRepo{
|
|
||||||
MockClonableRepository: mockRepo,
|
|
||||||
MockClonedRepository: mockCloned,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
operation: func(repo Repository, cloned bool) error {
|
|
||||||
require.True(t, cloned)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "clone failure",
|
|
||||||
setupMocks: func(t *testing.T) *mockClonableRepo {
|
|
||||||
mockRepo := NewMockClonableRepository(t)
|
|
||||||
|
|
||||||
mockRepo.EXPECT().Clone(mock.Anything, CloneOptions{}).Return(nil, errors.New("clone failed"))
|
|
||||||
|
|
||||||
return &mockClonableRepo{
|
|
||||||
MockClonableRepository: mockRepo,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
operation: func(repo Repository, cloned bool) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
expectedError: "clone repository: clone failed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "operation failure",
|
|
||||||
setupMocks: func(t *testing.T) *mockClonableRepo {
|
|
||||||
mockRepo := NewMockClonableRepository(t)
|
|
||||||
mockCloned := NewMockClonedRepository(t)
|
|
||||||
|
|
||||||
mockRepo.EXPECT().Clone(mock.Anything, CloneOptions{}).Return(mockCloned, nil)
|
|
||||||
mockCloned.EXPECT().Remove(mock.Anything).Return(nil)
|
|
||||||
|
|
||||||
return &mockClonableRepo{
|
|
||||||
MockClonableRepository: mockRepo,
|
|
||||||
MockClonedRepository: mockCloned,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
operation: func(repo Repository, cloned bool) error {
|
|
||||||
return errors.New("operation failed")
|
|
||||||
},
|
|
||||||
expectedError: "operation failed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "push failure",
|
|
||||||
setupMocks: func(t *testing.T) *mockClonableRepo {
|
|
||||||
mockRepo := NewMockClonableRepository(t)
|
|
||||||
mockCloned := NewMockClonedRepository(t)
|
|
||||||
|
|
||||||
mockRepo.EXPECT().Clone(mock.Anything, CloneOptions{}).Return(mockCloned, nil)
|
|
||||||
mockCloned.EXPECT().Push(mock.Anything, PushOptions{}).Return(errors.New("push failed"))
|
|
||||||
mockCloned.EXPECT().Remove(mock.Anything).Return(nil)
|
|
||||||
|
|
||||||
return &mockClonableRepo{
|
|
||||||
MockClonableRepository: mockRepo,
|
|
||||||
MockClonedRepository: mockCloned,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
operation: func(repo Repository, cloned bool) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
expectedError: "push failed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "remove failure should only log",
|
|
||||||
setupMocks: func(t *testing.T) *mockClonableRepo {
|
|
||||||
mockRepo := NewMockClonableRepository(t)
|
|
||||||
mockCloned := NewMockClonedRepository(t)
|
|
||||||
|
|
||||||
mockRepo.EXPECT().Clone(mock.Anything, CloneOptions{}).Return(mockCloned, nil)
|
|
||||||
mockCloned.EXPECT().Push(mock.Anything, PushOptions{}).Return(nil)
|
|
||||||
mockCloned.EXPECT().Remove(mock.Anything).Return(errors.New("remove failed"))
|
|
||||||
|
|
||||||
return &mockClonableRepo{
|
|
||||||
MockClonableRepository: mockRepo,
|
|
||||||
MockClonedRepository: mockCloned,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
operation: func(repo Repository, cloned bool) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
repo := tt.setupMocks(t)
|
|
||||||
err := WrapWithCloneAndPushIfPossible(context.Background(), repo, CloneOptions{}, PushOptions{}, tt.operation)
|
|
||||||
|
|
||||||
if tt.expectedError != "" {
|
|
||||||
require.EqualError(t, err, tt.expectedError)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// basicGitBranchNameRegex is a regular expression to validate a git branch name
|
||||||
|
// it does not cover all cases as positive lookaheads are not supported in Go's regexp
|
||||||
|
var basicGitBranchNameRegex = regexp.MustCompile(`^[a-zA-Z0-9\-\_\/\.]+$`)
|
||||||
|
|
||||||
|
// IsValidGitBranchName checks if a branch name is valid.
|
||||||
|
// It uses the following regexp `^[a-zA-Z0-9\-\_\/\.]+$` to validate the branch name with some additional checks that must satisfy the following rules:
|
||||||
|
// 1. The branch name must have at least one character and must not be empty.
|
||||||
|
// 2. The branch name cannot start with `/` or end with `/`, `.`, or whitespace.
|
||||||
|
// 3. The branch name cannot contain consecutive slashes (`//`).
|
||||||
|
// 4. The branch name cannot contain consecutive dots (`..`).
|
||||||
|
// 5. The branch name cannot contain `@{`.
|
||||||
|
// 6. The branch name cannot include the following characters: `~`, `^`, `:`, `?`, `*`, `[`, `\`, or `]`.
|
||||||
|
func IsValidGitBranchName(branch string) bool {
|
||||||
|
if !basicGitBranchNameRegex.MatchString(branch) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional checks for invalid patterns
|
||||||
|
if strings.HasPrefix(branch, "/") || strings.HasSuffix(branch, "/") ||
|
||||||
|
strings.HasSuffix(branch, ".") || strings.Contains(branch, "..") ||
|
||||||
|
strings.Contains(branch, "//") || strings.HasSuffix(branch, ".lock") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsValidGitBranchName(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
branch string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"Valid branch name", "feature/add-tests", true},
|
||||||
|
{"Valid branch name with numbers", "feature/123-add-tests", true},
|
||||||
|
{"Valid branch name with dots", "feature.add.tests", true},
|
||||||
|
{"Valid branch name with hyphens", "feature-add-tests", true},
|
||||||
|
{"Valid branch name with underscores", "feature_add_tests", true},
|
||||||
|
{"Valid branch name with mixed characters", "feature/add_tests-123", true},
|
||||||
|
{"Starts with /", "/feature", false},
|
||||||
|
{"Ends with /", "feature/", false},
|
||||||
|
{"Ends with .", "feature.", false},
|
||||||
|
{"Ends with space", "feature ", false},
|
||||||
|
{"Contains consecutive slashes", "feature//branch", false},
|
||||||
|
{"Contains consecutive dots", "feature..branch", false},
|
||||||
|
{"Contains @{", "feature@{branch", false},
|
||||||
|
{"Contains invalid character ~", "feature~branch", false},
|
||||||
|
{"Contains invalid character ^", "feature^branch", false},
|
||||||
|
{"Contains invalid character :", "feature:branch", false},
|
||||||
|
{"Contains invalid character ?", "feature?branch", false},
|
||||||
|
{"Contains invalid character *", "feature*branch", false},
|
||||||
|
{"Contains invalid character [", "feature[branch", false},
|
||||||
|
{"Contains invalid character ]", "feature]branch", false},
|
||||||
|
{"Contains invalid character \\", "feature\\branch", false},
|
||||||
|
{"Empty branch name", "", false},
|
||||||
|
{"Only whitespace", " ", false},
|
||||||
|
{"Single valid character", "a", true},
|
||||||
|
{"Ends with .lock", "feature.lock", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.expected, IsValidGitBranchName(tt.branch))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-6
@@ -1,15 +1,17 @@
|
|||||||
package repository
|
package git
|
||||||
|
|
||||||
|
import "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
|
|
||||||
// GitRepository is an interface that combines all repository capabilities
|
// GitRepository is an interface that combines all repository capabilities
|
||||||
// needed for Git repositories.
|
// needed for Git repositories.
|
||||||
//
|
//
|
||||||
//go:generate mockery --name GitRepository --structname MockGitRepository --inpackage --filename git_repository_mock.go --with-expecter
|
//go:generate mockery --name GitRepository --structname MockGitRepository --inpackage --filename git_repository_mock.go --with-expecter
|
||||||
type GitRepository interface {
|
type GitRepository interface {
|
||||||
Repository
|
repository.Repository
|
||||||
Versioned
|
repository.Versioned
|
||||||
Writer
|
repository.Writer
|
||||||
Reader
|
repository.Reader
|
||||||
ClonableRepository
|
repository.StageableRepository
|
||||||
URL() string
|
URL() string
|
||||||
Branch() string
|
Branch() string
|
||||||
}
|
}
|
||||||
+84
-82
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
package repository
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
mock "github.com/stretchr/testify/mock"
|
mock "github.com/stretchr/testify/mock"
|
||||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
|
||||||
|
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
|
|
||||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,83 +71,24 @@ func (_c *MockGitRepository_Branch_Call) RunAndReturn(run func() string) *MockGi
|
|||||||
return _c
|
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
|
// CompareFiles provides a mock function with given fields: ctx, base, ref
|
||||||
func (_m *MockGitRepository) CompareFiles(ctx context.Context, base string, ref string) ([]VersionedFileChange, error) {
|
func (_m *MockGitRepository) CompareFiles(ctx context.Context, base string, ref string) ([]repository.VersionedFileChange, error) {
|
||||||
ret := _m.Called(ctx, base, ref)
|
ret := _m.Called(ctx, base, ref)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for CompareFiles")
|
panic("no return value specified for CompareFiles")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 []VersionedFileChange
|
var r0 []repository.VersionedFileChange
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]VersionedFileChange, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]repository.VersionedFileChange, error)); ok {
|
||||||
return rf(ctx, base, ref)
|
return rf(ctx, base, ref)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []VersionedFileChange); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, string) []repository.VersionedFileChange); ok {
|
||||||
r0 = rf(ctx, base, ref)
|
r0 = rf(ctx, base, ref)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).([]VersionedFileChange)
|
r0 = ret.Get(0).([]repository.VersionedFileChange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,12 +121,12 @@ func (_c *MockGitRepository_CompareFiles_Call) Run(run func(ctx context.Context,
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGitRepository_CompareFiles_Call) Return(_a0 []VersionedFileChange, _a1 error) *MockGitRepository_CompareFiles_Call {
|
func (_c *MockGitRepository_CompareFiles_Call) Return(_a0 []repository.VersionedFileChange, _a1 error) *MockGitRepository_CompareFiles_Call {
|
||||||
_c.Call.Return(_a0, _a1)
|
_c.Call.Return(_a0, _a1)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGitRepository_CompareFiles_Call) RunAndReturn(run func(context.Context, string, string) ([]VersionedFileChange, error)) *MockGitRepository_CompareFiles_Call {
|
func (_c *MockGitRepository_CompareFiles_Call) RunAndReturn(run func(context.Context, string, string) ([]repository.VersionedFileChange, error)) *MockGitRepository_CompareFiles_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
@@ -451,23 +394,23 @@ func (_c *MockGitRepository_LatestRef_Call) RunAndReturn(run func(context.Contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read provides a mock function with given fields: ctx, path, ref
|
// Read provides a mock function with given fields: ctx, path, ref
|
||||||
func (_m *MockGitRepository) Read(ctx context.Context, path string, ref string) (*FileInfo, error) {
|
func (_m *MockGitRepository) Read(ctx context.Context, path string, ref string) (*repository.FileInfo, error) {
|
||||||
ret := _m.Called(ctx, path, ref)
|
ret := _m.Called(ctx, path, ref)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for Read")
|
panic("no return value specified for Read")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 *FileInfo
|
var r0 *repository.FileInfo
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*FileInfo, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*repository.FileInfo, error)); ok {
|
||||||
return rf(ctx, path, ref)
|
return rf(ctx, path, ref)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) *FileInfo); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, string) *repository.FileInfo); ok {
|
||||||
r0 = rf(ctx, path, ref)
|
r0 = rf(ctx, path, ref)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*FileInfo)
|
r0 = ret.Get(0).(*repository.FileInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,34 +443,34 @@ func (_c *MockGitRepository_Read_Call) Run(run func(ctx context.Context, path st
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGitRepository_Read_Call) Return(_a0 *FileInfo, _a1 error) *MockGitRepository_Read_Call {
|
func (_c *MockGitRepository_Read_Call) Return(_a0 *repository.FileInfo, _a1 error) *MockGitRepository_Read_Call {
|
||||||
_c.Call.Return(_a0, _a1)
|
_c.Call.Return(_a0, _a1)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGitRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*FileInfo, error)) *MockGitRepository_Read_Call {
|
func (_c *MockGitRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*repository.FileInfo, error)) *MockGitRepository_Read_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadTree provides a mock function with given fields: ctx, ref
|
// ReadTree provides a mock function with given fields: ctx, ref
|
||||||
func (_m *MockGitRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) {
|
func (_m *MockGitRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
||||||
ret := _m.Called(ctx, ref)
|
ret := _m.Called(ctx, ref)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for ReadTree")
|
panic("no return value specified for ReadTree")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 []FileTreeEntry
|
var r0 []repository.FileTreeEntry
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string) ([]FileTreeEntry, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string) ([]repository.FileTreeEntry, error)); ok {
|
||||||
return rf(ctx, ref)
|
return rf(ctx, ref)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string) []FileTreeEntry); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string) []repository.FileTreeEntry); ok {
|
||||||
r0 = rf(ctx, ref)
|
r0 = rf(ctx, ref)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).([]FileTreeEntry)
|
r0 = ret.Get(0).([]repository.FileTreeEntry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,12 +502,71 @@ func (_c *MockGitRepository_ReadTree_Call) Run(run func(ctx context.Context, ref
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGitRepository_ReadTree_Call) Return(_a0 []FileTreeEntry, _a1 error) *MockGitRepository_ReadTree_Call {
|
func (_c *MockGitRepository_ReadTree_Call) Return(_a0 []repository.FileTreeEntry, _a1 error) *MockGitRepository_ReadTree_Call {
|
||||||
_c.Call.Return(_a0, _a1)
|
_c.Call.Return(_a0, _a1)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGitRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]FileTreeEntry, error)) *MockGitRepository_ReadTree_Call {
|
func (_c *MockGitRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]repository.FileTreeEntry, error)) *MockGitRepository_ReadTree_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage provides a mock function with given fields: ctx, opts
|
||||||
|
func (_m *MockGitRepository) Stage(ctx context.Context, opts repository.StageOptions) (repository.StagedRepository, error) {
|
||||||
|
ret := _m.Called(ctx, opts)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Stage")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 repository.StagedRepository
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, repository.StageOptions) (repository.StagedRepository, error)); ok {
|
||||||
|
return rf(ctx, opts)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, repository.StageOptions) repository.StagedRepository); ok {
|
||||||
|
r0 = rf(ctx, opts)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(repository.StagedRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, repository.StageOptions) error); ok {
|
||||||
|
r1 = rf(ctx, opts)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockGitRepository_Stage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stage'
|
||||||
|
type MockGitRepository_Stage_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - opts repository.StageOptions
|
||||||
|
func (_e *MockGitRepository_Expecter) Stage(ctx interface{}, opts interface{}) *MockGitRepository_Stage_Call {
|
||||||
|
return &MockGitRepository_Stage_Call{Call: _e.mock.On("Stage", ctx, opts)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockGitRepository_Stage_Call) Run(run func(ctx context.Context, opts repository.StageOptions)) *MockGitRepository_Stage_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(repository.StageOptions))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockGitRepository_Stage_Call) Return(_a0 repository.StagedRepository, _a1 error) *MockGitRepository_Stage_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockGitRepository_Stage_Call) RunAndReturn(run func(context.Context, repository.StageOptions) (repository.StagedRepository, error)) *MockGitRepository_Stage_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
+27
-25
@@ -1,12 +1,14 @@
|
|||||||
package nanogit
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
@@ -17,7 +19,6 @@ import (
|
|||||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
"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"
|
||||||
"github.com/grafana/nanogit/log"
|
"github.com/grafana/nanogit/log"
|
||||||
"github.com/grafana/nanogit/options"
|
"github.com/grafana/nanogit/options"
|
||||||
@@ -38,28 +39,19 @@ type gitRepository struct {
|
|||||||
config *provisioning.Repository
|
config *provisioning.Repository
|
||||||
gitConfig RepositoryConfig
|
gitConfig RepositoryConfig
|
||||||
client nanogit.Client
|
client nanogit.Client
|
||||||
secrets secrets.Service
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGitRepository(
|
func NewGitRepository(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
secrets secrets.Service,
|
|
||||||
config *provisioning.Repository,
|
config *provisioning.Repository,
|
||||||
gitConfig RepositoryConfig,
|
gitConfig RepositoryConfig,
|
||||||
) (repository.GitRepository, error) {
|
) (GitRepository, error) {
|
||||||
if gitConfig.Token == "" {
|
var opts []options.Option
|
||||||
decrypted, err := secrets.Decrypt(ctx, gitConfig.EncryptedToken)
|
if len(gitConfig.Token) > 0 {
|
||||||
if err != nil {
|
opts = append(opts, options.WithBasicAuth("git", gitConfig.Token))
|
||||||
return nil, fmt.Errorf("decrypt token: %w", err)
|
|
||||||
}
|
|
||||||
gitConfig.Token = string(decrypted)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create nanogit client with authentication
|
client, err := nanogit.NewHTTPClient(gitConfig.URL, opts...)
|
||||||
client, err := nanogit.NewHTTPClient(
|
|
||||||
gitConfig.URL,
|
|
||||||
options.WithBasicAuth("git", gitConfig.Token),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create nanogit client: %w", err)
|
return nil, fmt.Errorf("create nanogit client: %w", err)
|
||||||
}
|
}
|
||||||
@@ -68,7 +60,6 @@ func NewGitRepository(
|
|||||||
config: config,
|
config: config,
|
||||||
gitConfig: gitConfig,
|
gitConfig: gitConfig,
|
||||||
client: client,
|
client: client,
|
||||||
secrets: secrets,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,12 +89,15 @@ func (r *gitRepository) Validate() (list field.ErrorList) {
|
|||||||
}
|
}
|
||||||
if cfg.Branch == "" {
|
if cfg.Branch == "" {
|
||||||
list = append(list, field.Required(field.NewPath("spec", t, "branch"), "a git branch is required"))
|
list = append(list, field.Required(field.NewPath("spec", t, "branch"), "a git branch is required"))
|
||||||
} else if !repository.IsValidGitBranchName(cfg.Branch) {
|
} else if !IsValidGitBranchName(cfg.Branch) {
|
||||||
list = append(list, field.Invalid(field.NewPath("spec", t, "branch"), cfg.Branch, "invalid branch name"))
|
list = append(list, field.Invalid(field.NewPath("spec", t, "branch"), cfg.Branch, "invalid branch name"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Token == "" && len(cfg.EncryptedToken) == 0 {
|
// If the repository has workflows, we require a token or encrypted token
|
||||||
list = append(list, field.Required(field.NewPath("spec", t, "token"), "a git access token is required"))
|
if len(r.config.Spec.Workflows) > 0 {
|
||||||
|
if cfg.Token == "" && len(cfg.EncryptedToken) == 0 {
|
||||||
|
list = append(list, field.Required(field.NewPath("spec", t, "token"), "a git access token is required"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := safepath.IsSafe(cfg.Path); err != nil {
|
if err := safepath.IsSafe(cfg.Path); err != nil {
|
||||||
@@ -412,11 +406,15 @@ func (r *gitRepository) Write(ctx context.Context, path string, ref string, data
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx, _ = r.logger(ctx, ref)
|
ctx, _ = r.logger(ctx, ref)
|
||||||
_, err := r.Read(ctx, path, ref)
|
info, err := r.Read(ctx, path, ref)
|
||||||
if err != nil && !(errors.Is(err, repository.ErrFileNotFound)) {
|
if err != nil && !(errors.Is(err, repository.ErrFileNotFound)) {
|
||||||
return fmt.Errorf("check if file exists before writing: %w", err)
|
return fmt.Errorf("check if file exists before writing: %w", err)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
// If the value already exists and is the same, we don't need to do anything
|
||||||
|
if bytes.Equal(info.Data, data) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return r.Update(ctx, path, ref, data, message)
|
return r.Update(ctx, path, ref, data, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +448,8 @@ func (r *gitRepository) delete(ctx context.Context, path string, writer nanogit.
|
|||||||
finalPath := safepath.Join(r.gitConfig.Path, path)
|
finalPath := safepath.Join(r.gitConfig.Path, path)
|
||||||
// Check if it's a directory - use DeleteTree for directories, DeleteBlob for files
|
// Check if it's a directory - use DeleteTree for directories, DeleteBlob for files
|
||||||
if safepath.IsDir(path) {
|
if safepath.IsDir(path) {
|
||||||
if _, err := writer.DeleteTree(ctx, finalPath); err != nil {
|
trimmed := strings.TrimSuffix(finalPath, "/")
|
||||||
|
if _, err := writer.DeleteTree(ctx, trimmed); err != nil {
|
||||||
if errors.Is(err, nanogit.ErrObjectNotFound) {
|
if errors.Is(err, nanogit.ErrObjectNotFound) {
|
||||||
return repository.ErrFileNotFound
|
return repository.ErrFileNotFound
|
||||||
}
|
}
|
||||||
@@ -582,7 +581,7 @@ func (r *gitRepository) CompareFiles(ctx context.Context, base, ref string) ([]r
|
|||||||
return changes, nil
|
return changes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *gitRepository) Clone(ctx context.Context, opts repository.CloneOptions) (repository.ClonedRepository, error) {
|
func (r *gitRepository) Stage(ctx context.Context, opts repository.StageOptions) (repository.StagedRepository, error) {
|
||||||
return NewStagedGitRepository(ctx, r, opts)
|
return NewStagedGitRepository(ctx, r, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,7 +589,7 @@ func (r *gitRepository) Clone(ctx context.Context, opts repository.CloneOptions)
|
|||||||
func (r *gitRepository) resolveRefToHash(ctx context.Context, ref string) (hash.Hash, error) {
|
func (r *gitRepository) resolveRefToHash(ctx context.Context, ref string) (hash.Hash, error) {
|
||||||
// Use default branch if ref is empty
|
// Use default branch if ref is empty
|
||||||
if ref == "" {
|
if ref == "" {
|
||||||
ref = fmt.Sprintf("refs/heads/%s", r.gitConfig.Branch)
|
ref = r.gitConfig.Branch
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to parse ref as a hash first
|
// Try to parse ref as a hash first
|
||||||
@@ -600,6 +599,9 @@ func (r *gitRepository) resolveRefToHash(ctx context.Context, ref string) (hash.
|
|||||||
return refHash, nil
|
return refHash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefix ref with refs/heads/
|
||||||
|
ref = fmt.Sprintf("refs/heads/%s", ref)
|
||||||
|
|
||||||
// Not a valid hash, try to resolve as a branch reference
|
// Not a valid hash, try to resolve as a branch reference
|
||||||
branchRef, err := r.client.GetRef(ctx, ref)
|
branchRef, err := r.client.GetRef(ctx, ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -615,7 +617,7 @@ func (r *gitRepository) resolveRefToHash(ctx context.Context, ref string) (hash.
|
|||||||
// ensureBranchExists checks if a branch exists and creates it if it doesn't,
|
// ensureBranchExists checks if a branch exists and creates it if it doesn't,
|
||||||
// returning the branch reference to avoid duplicate GetRef calls
|
// returning the branch reference to avoid duplicate GetRef calls
|
||||||
func (r *gitRepository) ensureBranchExists(ctx context.Context, branchName string) (nanogit.Ref, error) {
|
func (r *gitRepository) ensureBranchExists(ctx context.Context, branchName string) (nanogit.Ref, error) {
|
||||||
if !repository.IsValidGitBranchName(branchName) {
|
if !IsValidGitBranchName(branchName) {
|
||||||
return nanogit.Ref{}, &apierrors.StatusError{
|
return nanogit.Ref{}, &apierrors.StatusError{
|
||||||
ErrStatus: metav1.Status{
|
ErrStatus: metav1.Status{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
+66
-143
@@ -1,4 +1,4 @@
|
|||||||
package nanogit
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -7,17 +7,17 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
|
||||||
"github.com/grafana/nanogit"
|
|
||||||
"github.com/grafana/nanogit/mocks"
|
|
||||||
"github.com/grafana/nanogit/protocol"
|
|
||||||
"github.com/grafana/nanogit/protocol/hash"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
|
||||||
|
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
|
"github.com/grafana/nanogit"
|
||||||
|
"github.com/grafana/nanogit/mocks"
|
||||||
|
"github.com/grafana/nanogit/protocol"
|
||||||
|
"github.com/grafana/nanogit/protocol/hash"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGitRepository_Validate(t *testing.T) {
|
func TestGitRepository_Validate(t *testing.T) {
|
||||||
@@ -138,10 +138,11 @@ func TestGitRepository_Validate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing token",
|
name: "missing token for R/W repository",
|
||||||
config: &provisioning.Repository{
|
config: &provisioning.Repository{
|
||||||
Spec: provisioning.RepositorySpec{
|
Spec: provisioning.RepositorySpec{
|
||||||
Type: "test_type",
|
Type: "test_type",
|
||||||
|
Workflows: []provisioning.Workflow{provisioning.WriteWorkflow},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
gitConfig: RepositoryConfig{
|
gitConfig: RepositoryConfig{
|
||||||
@@ -153,6 +154,21 @@ func TestGitRepository_Validate(t *testing.T) {
|
|||||||
field.Required(field.NewPath("spec", "test_type", "token"), "a git access token is required"),
|
field.Required(field.NewPath("spec", "test_type", "token"), "a git access token is required"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "missing token for read-only repository",
|
||||||
|
config: &provisioning.Repository{
|
||||||
|
Spec: provisioning.RepositorySpec{
|
||||||
|
Type: "test_type",
|
||||||
|
Workflows: nil, // read-only
|
||||||
|
},
|
||||||
|
},
|
||||||
|
gitConfig: RepositoryConfig{
|
||||||
|
URL: "https://git.example.com/repo.git",
|
||||||
|
Branch: "main",
|
||||||
|
Token: "", // Empty token
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "unsafe path",
|
name: "unsafe path",
|
||||||
config: &provisioning.Repository{
|
config: &provisioning.Repository{
|
||||||
@@ -258,20 +274,8 @@ func TestIsValidGitURL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock secrets service for testing
|
|
||||||
type mockSecretsService struct{}
|
|
||||||
|
|
||||||
func (m *mockSecretsService) Decrypt(ctx context.Context, data []byte) ([]byte, error) {
|
|
||||||
return []byte("decrypted-token"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockSecretsService) Encrypt(ctx context.Context, data []byte) ([]byte, error) {
|
|
||||||
return []byte("encrypted-token"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewGit(t *testing.T) {
|
func TestNewGit(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
mockSecrets := &mockSecretsService{}
|
|
||||||
|
|
||||||
config := &provisioning.Repository{
|
config := &provisioning.Repository{
|
||||||
Spec: provisioning.RepositorySpec{
|
Spec: provisioning.RepositorySpec{
|
||||||
@@ -288,7 +292,7 @@ func TestNewGit(t *testing.T) {
|
|||||||
|
|
||||||
// This should succeed in creating the client but won't be able to connect
|
// This should succeed in creating the client but won't be able to connect
|
||||||
// We just test that the basic structure is created correctly
|
// We just test that the basic structure is created correctly
|
||||||
gitRepo, err := NewGitRepository(ctx, mockSecrets, config, gitConfig)
|
gitRepo, err := NewGitRepository(ctx, config, gitConfig)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, gitRepo)
|
require.NotNil(t, gitRepo)
|
||||||
require.Equal(t, "https://git.example.com/owner/repo.git", gitRepo.URL())
|
require.Equal(t, "https://git.example.com/owner/repo.git", gitRepo.URL())
|
||||||
@@ -1073,27 +1077,27 @@ func TestGitRepository_Update(t *testing.T) {
|
|||||||
|
|
||||||
func TestGitRepository_Delete(t *testing.T) {
|
func TestGitRepository_Delete(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setupMock func(*mocks.FakeClient)
|
setupMock func(*mocks.FakeClient, *mocks.FakeStagedWriter)
|
||||||
gitConfig RepositoryConfig
|
assertions func(*testing.T, *mocks.FakeClient, *mocks.FakeStagedWriter)
|
||||||
path string
|
gitConfig RepositoryConfig
|
||||||
ref string
|
path string
|
||||||
comment string
|
ref string
|
||||||
wantError bool
|
comment string
|
||||||
errorType error
|
wantError bool
|
||||||
|
errorType error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "success - delete file",
|
name: "success - delete file",
|
||||||
setupMock: func(mockClient *mocks.FakeClient) {
|
setupMock: func(mockClient *mocks.FakeClient, mockWriter *mocks.FakeStagedWriter) {
|
||||||
mockClient.GetRefReturns(nanogit.Ref{
|
mockClient.GetRefReturns(nanogit.Ref{
|
||||||
Name: "refs/heads/main",
|
Name: "refs/heads/main",
|
||||||
Hash: hash.Hash{},
|
Hash: hash.Hash{},
|
||||||
}, nil)
|
}, nil)
|
||||||
mockWriter := &mocks.FakeStagedWriter{}
|
|
||||||
mockWriter.DeleteBlobReturns(hash.Hash{}, nil)
|
mockWriter.DeleteBlobReturns(hash.Hash{}, nil)
|
||||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||||
mockWriter.PushReturns(nil)
|
mockWriter.PushReturns(nil)
|
||||||
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
|
||||||
},
|
},
|
||||||
gitConfig: RepositoryConfig{
|
gitConfig: RepositoryConfig{
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
@@ -1106,16 +1110,19 @@ func TestGitRepository_Delete(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "success - delete directory",
|
name: "success - delete directory",
|
||||||
setupMock: func(mockClient *mocks.FakeClient) {
|
setupMock: func(mockClient *mocks.FakeClient, mockWriter *mocks.FakeStagedWriter) {
|
||||||
mockClient.GetRefReturns(nanogit.Ref{
|
mockClient.GetRefReturns(nanogit.Ref{
|
||||||
Name: "refs/heads/main",
|
Name: "refs/heads/main",
|
||||||
Hash: hash.Hash{},
|
Hash: hash.Hash{},
|
||||||
}, nil)
|
}, nil)
|
||||||
mockWriter := &mocks.FakeStagedWriter{}
|
|
||||||
mockWriter.DeleteTreeReturns(hash.Hash{}, nil)
|
mockWriter.DeleteTreeReturns(hash.Hash{}, nil)
|
||||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||||
mockWriter.PushReturns(nil)
|
mockWriter.PushReturns(nil)
|
||||||
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
},
|
||||||
|
assertions: func(t *testing.T, fakeClient *mocks.FakeClient, mockWriter *mocks.FakeStagedWriter) {
|
||||||
|
require.Equal(t, 1, mockWriter.DeleteTreeCallCount(), "DeleteTree should be called once")
|
||||||
|
_, p := mockWriter.DeleteTreeArgsForCall(0)
|
||||||
|
require.Equal(t, "configs/testdir", p, "DeleteTree should be called with correct path")
|
||||||
},
|
},
|
||||||
gitConfig: RepositoryConfig{
|
gitConfig: RepositoryConfig{
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
@@ -1128,14 +1135,12 @@ func TestGitRepository_Delete(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "failure - file not found",
|
name: "failure - file not found",
|
||||||
setupMock: func(mockClient *mocks.FakeClient) {
|
setupMock: func(mockClient *mocks.FakeClient, mockWriter *mocks.FakeStagedWriter) {
|
||||||
mockClient.GetRefReturns(nanogit.Ref{
|
mockClient.GetRefReturns(nanogit.Ref{
|
||||||
Name: "refs/heads/main",
|
Name: "refs/heads/main",
|
||||||
Hash: hash.Hash{},
|
Hash: hash.Hash{},
|
||||||
}, nil)
|
}, nil)
|
||||||
mockWriter := &mocks.FakeStagedWriter{}
|
|
||||||
mockWriter.DeleteBlobReturns(hash.Hash{}, nanogit.ErrObjectNotFound)
|
mockWriter.DeleteBlobReturns(hash.Hash{}, nanogit.ErrObjectNotFound)
|
||||||
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
|
||||||
},
|
},
|
||||||
gitConfig: RepositoryConfig{
|
gitConfig: RepositoryConfig{
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
@@ -1152,7 +1157,10 @@ func TestGitRepository_Delete(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
mockClient := &mocks.FakeClient{}
|
mockClient := &mocks.FakeClient{}
|
||||||
tt.setupMock(mockClient)
|
mockWriter := &mocks.FakeStagedWriter{}
|
||||||
|
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
||||||
|
|
||||||
|
tt.setupMock(mockClient, mockWriter)
|
||||||
|
|
||||||
gitRepo := &gitRepository{
|
gitRepo := &gitRepository{
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
@@ -1174,6 +1182,10 @@ func TestGitRepository_Delete(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tt.assertions != nil {
|
||||||
|
tt.assertions(t, mockClient, mockWriter)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1729,57 +1741,21 @@ func TestGitRepository_createSignature(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewGitRepository(t *testing.T) {
|
func TestNewGitRepository(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setupMock func(*mockSecretsService)
|
gitConfig RepositoryConfig
|
||||||
gitConfig RepositoryConfig
|
wantError bool
|
||||||
wantError bool
|
expectURL string
|
||||||
expectURL string
|
|
||||||
expectToken string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "success - with token",
|
name: "success - with token",
|
||||||
setupMock: func(mockSecrets *mockSecretsService) {
|
|
||||||
// No setup needed for token case
|
|
||||||
},
|
|
||||||
gitConfig: RepositoryConfig{
|
gitConfig: RepositoryConfig{
|
||||||
URL: "https://git.example.com/owner/repo.git",
|
URL: "https://git.example.com/owner/repo.git",
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
Token: "plain-token",
|
Token: "plain-token",
|
||||||
Path: "configs",
|
Path: "configs",
|
||||||
},
|
},
|
||||||
wantError: false,
|
wantError: false,
|
||||||
expectURL: "https://git.example.com/owner/repo.git",
|
expectURL: "https://git.example.com/owner/repo.git",
|
||||||
expectToken: "plain-token",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "success - with encrypted token",
|
|
||||||
setupMock: func(mockSecrets *mockSecretsService) {
|
|
||||||
// Mock will return decrypted token
|
|
||||||
},
|
|
||||||
gitConfig: RepositoryConfig{
|
|
||||||
URL: "https://git.example.com/owner/repo.git",
|
|
||||||
Branch: "main",
|
|
||||||
Token: "", // Empty token, will use encrypted
|
|
||||||
EncryptedToken: []byte("encrypted-token"),
|
|
||||||
Path: "configs",
|
|
||||||
},
|
|
||||||
wantError: false,
|
|
||||||
expectURL: "https://git.example.com/owner/repo.git",
|
|
||||||
expectToken: "decrypted-token", // From mock
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "failure - decryption error",
|
|
||||||
setupMock: func(mockSecrets *mockSecretsService) {
|
|
||||||
// This test will use the separate mockSecretsServiceWithError
|
|
||||||
},
|
|
||||||
gitConfig: RepositoryConfig{
|
|
||||||
URL: "https://git.example.com/owner/repo.git",
|
|
||||||
Branch: "main",
|
|
||||||
Token: "",
|
|
||||||
EncryptedToken: []byte("bad-encrypted-token"),
|
|
||||||
Path: "configs",
|
|
||||||
},
|
|
||||||
wantError: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1787,20 +1763,13 @@ func TestNewGitRepository(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
var mockSecrets secrets.Service
|
|
||||||
if tt.name == "failure - decryption error" {
|
|
||||||
mockSecrets = &mockSecretsServiceWithError{shouldError: true}
|
|
||||||
} else {
|
|
||||||
mockSecrets = &mockSecretsService{}
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &provisioning.Repository{
|
config := &provisioning.Repository{
|
||||||
Spec: provisioning.RepositorySpec{
|
Spec: provisioning.RepositorySpec{
|
||||||
Type: provisioning.GitHubRepositoryType,
|
Type: provisioning.GitHubRepositoryType,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
gitRepo, err := NewGitRepository(ctx, mockSecrets, config, tt.gitConfig)
|
gitRepo, err := NewGitRepository(ctx, config, tt.gitConfig)
|
||||||
|
|
||||||
if tt.wantError {
|
if tt.wantError {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -2168,7 +2137,7 @@ func TestGitRepository_logger(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitRepository_Clone(t *testing.T) {
|
func TestGitRepository_Stage(t *testing.T) {
|
||||||
gitRepo := &gitRepository{
|
gitRepo := &gitRepository{
|
||||||
config: &provisioning.Repository{
|
config: &provisioning.Repository{
|
||||||
Spec: provisioning.RepositorySpec{
|
Spec: provisioning.RepositorySpec{
|
||||||
@@ -2184,9 +2153,8 @@ func TestGitRepository_Clone(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("calls NewStagedGitRepository", func(t *testing.T) {
|
t.Run("calls NewStagedGitRepository", func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
opts := repository.CloneOptions{
|
opts := repository.StageOptions{
|
||||||
CreateIfNotExists: true,
|
PushOnWrites: true,
|
||||||
PushOnWrites: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since NewStagedGitRepository is not mocked and may panic, we expect this to fail
|
// Since NewStagedGitRepository is not mocked and may panic, we expect this to fail
|
||||||
@@ -2198,11 +2166,11 @@ func TestGitRepository_Clone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
cloned, err := gitRepo.Clone(ctx, opts)
|
staged, err := gitRepo.Stage(ctx, opts)
|
||||||
|
|
||||||
// This will likely error/panic since we don't have a real implementation
|
// This will likely error/panic since we don't have a real implementation
|
||||||
// but we're testing that the method exists and forwards to NewStagedGitRepository
|
// but we're testing that the method exists and forwards to NewStagedGitRepository
|
||||||
_ = cloned
|
_ = staged
|
||||||
_ = err
|
_ = err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2271,50 +2239,6 @@ func TestGitRepository_EdgeCases(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enhanced secrets service mock with error handling
|
|
||||||
type mockSecretsServiceWithError struct {
|
|
||||||
shouldError bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockSecretsServiceWithError) Decrypt(ctx context.Context, data []byte) ([]byte, error) {
|
|
||||||
if m.shouldError {
|
|
||||||
return nil, errors.New("decryption failed")
|
|
||||||
}
|
|
||||||
return []byte("decrypted-token"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockSecretsServiceWithError) Encrypt(ctx context.Context, data []byte) ([]byte, error) {
|
|
||||||
if m.shouldError {
|
|
||||||
return nil, errors.New("encryption failed")
|
|
||||||
}
|
|
||||||
return []byte("encrypted-token"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewGitRepository_DecryptionError(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
mockSecrets := &mockSecretsServiceWithError{shouldError: true}
|
|
||||||
|
|
||||||
config := &provisioning.Repository{
|
|
||||||
Spec: provisioning.RepositorySpec{
|
|
||||||
Type: provisioning.GitHubRepositoryType,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
gitConfig := RepositoryConfig{
|
|
||||||
URL: "https://git.example.com/owner/repo.git",
|
|
||||||
Branch: "main",
|
|
||||||
Token: "",
|
|
||||||
EncryptedToken: []byte("encrypted-token"),
|
|
||||||
Path: "configs",
|
|
||||||
}
|
|
||||||
|
|
||||||
gitRepo, err := NewGitRepository(ctx, mockSecrets, config, gitConfig)
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Nil(t, gitRepo)
|
|
||||||
require.Contains(t, err.Error(), "decrypt token")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGitRepository_ValidateBranchNames(t *testing.T) {
|
func TestGitRepository_ValidateBranchNames(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -2782,7 +2706,6 @@ func TestGitRepository_NewGitRepository_ClientError(t *testing.T) {
|
|||||||
// This test would require mocking nanogit.NewHTTPClient which is difficult
|
// This test would require mocking nanogit.NewHTTPClient which is difficult
|
||||||
// We test the path where the client creation would fail by using invalid URL
|
// We test the path where the client creation would fail by using invalid URL
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
mockSecrets := &mockSecretsService{}
|
|
||||||
|
|
||||||
config := &provisioning.Repository{
|
config := &provisioning.Repository{
|
||||||
Spec: provisioning.RepositorySpec{
|
Spec: provisioning.RepositorySpec{
|
||||||
@@ -2797,7 +2720,7 @@ func TestGitRepository_NewGitRepository_ClientError(t *testing.T) {
|
|||||||
Path: "configs",
|
Path: "configs",
|
||||||
}
|
}
|
||||||
|
|
||||||
gitRepo, err := NewGitRepository(ctx, mockSecrets, config, gitConfig)
|
gitRepo, err := NewGitRepository(ctx, config, gitConfig)
|
||||||
|
|
||||||
// We expect this to fail during client creation
|
// We expect this to fail during client creation
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
+21
-26
@@ -1,4 +1,4 @@
|
|||||||
package nanogit
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -15,17 +15,11 @@ import (
|
|||||||
// once that happens we could do more magic here.
|
// once that happens we could do more magic here.
|
||||||
type stagedGitRepository struct {
|
type stagedGitRepository struct {
|
||||||
*gitRepository
|
*gitRepository
|
||||||
opts repository.CloneOptions
|
opts repository.StageOptions
|
||||||
writer nanogit.StagedWriter
|
writer nanogit.StagedWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStagedGitRepository(ctx context.Context, repo *gitRepository, opts repository.CloneOptions) (repository.ClonedRepository, error) {
|
func NewStagedGitRepository(ctx context.Context, repo *gitRepository, opts repository.StageOptions) (repository.StagedRepository, error) {
|
||||||
if opts.BeforeFn != nil {
|
|
||||||
if err := opts.BeforeFn(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Timeout > 0 {
|
if opts.Timeout > 0 {
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
|
ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
|
||||||
@@ -89,28 +83,35 @@ func (r *stagedGitRepository) Create(ctx context.Context, path, ref string, data
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r.opts.PushOnWrites {
|
if r.opts.PushOnWrites {
|
||||||
return r.Push(ctx, repository.PushOptions{})
|
return r.Push(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *stagedGitRepository) blobExists(ctx context.Context, path string) (bool, error) {
|
||||||
|
if r.gitConfig.Path != "" {
|
||||||
|
path = safepath.Join(r.gitConfig.Path, path)
|
||||||
|
}
|
||||||
|
return r.writer.BlobExists(ctx, path)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *stagedGitRepository) Write(ctx context.Context, path, ref string, data []byte, message string) error {
|
func (r *stagedGitRepository) Write(ctx context.Context, path, ref string, data []byte, message string) error {
|
||||||
if ref != "" && ref != r.gitConfig.Branch {
|
if ref != "" && ref != r.gitConfig.Branch {
|
||||||
return errors.New("ref is not supported for staged repository")
|
return errors.New("ref is not supported for staged repository")
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err := r.writer.BlobExists(ctx, path)
|
exists, err := r.blobExists(ctx, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("check if file exists: %w", err)
|
return fmt.Errorf("check if file exists: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if exists {
|
||||||
if err := r.create(ctx, path, data, r.writer); err != nil {
|
if err := r.update(ctx, path, data, r.writer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := r.update(ctx, path, data, r.writer); err != nil {
|
if err := r.create(ctx, path, data, r.writer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,7 +121,7 @@ func (r *stagedGitRepository) Write(ctx context.Context, path, ref string, data
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r.opts.PushOnWrites {
|
if r.opts.PushOnWrites {
|
||||||
return r.Push(ctx, repository.PushOptions{})
|
return r.Push(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -144,7 +145,7 @@ func (r *stagedGitRepository) Update(ctx context.Context, path, ref string, data
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r.opts.PushOnWrites {
|
if r.opts.PushOnWrites {
|
||||||
return r.Push(ctx, repository.PushOptions{})
|
return r.Push(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -164,22 +165,16 @@ func (r *stagedGitRepository) Delete(ctx context.Context, path, ref, message str
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r.opts.PushOnWrites {
|
if r.opts.PushOnWrites {
|
||||||
return r.Push(ctx, repository.PushOptions{})
|
return r.Push(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *stagedGitRepository) Push(ctx context.Context, opts repository.PushOptions) error {
|
func (r *stagedGitRepository) Push(ctx context.Context) error {
|
||||||
if opts.BeforeFn != nil {
|
if r.opts.Timeout > 0 {
|
||||||
if err := opts.BeforeFn(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Timeout > 0 {
|
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
|
ctx, cancel = context.WithTimeout(ctx, r.opts.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
+46
-95
@@ -1,4 +1,4 @@
|
|||||||
package nanogit
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -18,7 +18,7 @@ func TestNewStagedGitRepository(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setupMock func(*mocks.FakeClient)
|
setupMock func(*mocks.FakeClient)
|
||||||
opts repository.CloneOptions
|
opts repository.StageOptions
|
||||||
wantError error
|
wantError error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -31,9 +31,8 @@ func TestNewStagedGitRepository(t *testing.T) {
|
|||||||
mockWriter := &mocks.FakeStagedWriter{}
|
mockWriter := &mocks.FakeStagedWriter{}
|
||||||
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
CreateIfNotExists: false,
|
PushOnWrites: false,
|
||||||
PushOnWrites: false,
|
|
||||||
},
|
},
|
||||||
wantError: nil,
|
wantError: nil,
|
||||||
},
|
},
|
||||||
@@ -47,12 +46,8 @@ func TestNewStagedGitRepository(t *testing.T) {
|
|||||||
mockWriter := &mocks.FakeStagedWriter{}
|
mockWriter := &mocks.FakeStagedWriter{}
|
||||||
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
CreateIfNotExists: false,
|
PushOnWrites: false,
|
||||||
PushOnWrites: false,
|
|
||||||
BeforeFn: func() error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
wantError: nil,
|
wantError: nil,
|
||||||
},
|
},
|
||||||
@@ -66,33 +61,19 @@ func TestNewStagedGitRepository(t *testing.T) {
|
|||||||
mockWriter := &mocks.FakeStagedWriter{}
|
mockWriter := &mocks.FakeStagedWriter{}
|
||||||
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
mockClient.NewStagedWriterReturns(mockWriter, nil)
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
CreateIfNotExists: false,
|
PushOnWrites: false,
|
||||||
PushOnWrites: false,
|
Timeout: time.Second * 5,
|
||||||
Timeout: time.Second * 5,
|
|
||||||
},
|
},
|
||||||
wantError: nil,
|
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",
|
name: "fails with GetRef error",
|
||||||
setupMock: func(mockClient *mocks.FakeClient) {
|
setupMock: func(mockClient *mocks.FakeClient) {
|
||||||
mockClient.GetRefReturns(nanogit.Ref{}, errors.New("ref not found"))
|
mockClient.GetRefReturns(nanogit.Ref{}, errors.New("ref not found"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
CreateIfNotExists: false,
|
PushOnWrites: false,
|
||||||
PushOnWrites: false,
|
|
||||||
},
|
},
|
||||||
wantError: errors.New("ref not found"),
|
wantError: errors.New("ref not found"),
|
||||||
},
|
},
|
||||||
@@ -105,9 +86,8 @@ func TestNewStagedGitRepository(t *testing.T) {
|
|||||||
}, nil)
|
}, nil)
|
||||||
mockClient.NewStagedWriterReturns(nil, errors.New("failed to create writer"))
|
mockClient.NewStagedWriterReturns(nil, errors.New("failed to create writer"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
CreateIfNotExists: false,
|
PushOnWrites: false,
|
||||||
PushOnWrites: false,
|
|
||||||
},
|
},
|
||||||
wantError: errors.New("build staged writer: failed to create writer"),
|
wantError: errors.New("build staged writer: failed to create writer"),
|
||||||
},
|
},
|
||||||
@@ -141,13 +121,8 @@ func TestNewStagedGitRepository(t *testing.T) {
|
|||||||
|
|
||||||
// Compare opts fields individually since function pointers can't be compared directly
|
// Compare opts fields individually since function pointers can't be compared directly
|
||||||
actualOpts := stagedRepo.(*stagedGitRepository).opts
|
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.PushOnWrites, actualOpts.PushOnWrites)
|
||||||
require.Equal(t, tt.opts.MaxSize, actualOpts.MaxSize)
|
|
||||||
require.Equal(t, tt.opts.Timeout, actualOpts.Timeout)
|
require.Equal(t, tt.opts.Timeout, actualOpts.Timeout)
|
||||||
require.Equal(t, tt.opts.Progress, actualOpts.Progress)
|
|
||||||
// BeforeFn is a function pointer, so we just check if both are nil or both are not nil
|
|
||||||
require.Equal(t, tt.opts.BeforeFn == nil, actualOpts.BeforeFn == nil)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -287,7 +262,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setupMock func(*mocks.FakeStagedWriter)
|
setupMock func(*mocks.FakeStagedWriter)
|
||||||
opts repository.CloneOptions
|
opts repository.StageOptions
|
||||||
path string
|
path string
|
||||||
ref string
|
ref string
|
||||||
data []byte
|
data []byte
|
||||||
@@ -301,7 +276,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
|||||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -318,7 +293,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
|||||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||||
mockWriter.PushReturns(nil)
|
mockWriter.PushReturns(nil)
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: true,
|
PushOnWrites: true,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -333,7 +308,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
|||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
// No setup needed as error occurs before writer calls
|
// No setup needed as error occurs before writer calls
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -347,7 +322,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
|||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
mockWriter.CreateBlobReturns(hash.Hash{}, errors.New("create blob failed"))
|
mockWriter.CreateBlobReturns(hash.Hash{}, errors.New("create blob failed"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -362,7 +337,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
|||||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||||
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -378,7 +353,7 @@ func TestStagedGitRepository_Create(t *testing.T) {
|
|||||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||||
mockWriter.PushReturns(errors.New("push failed"))
|
mockWriter.PushReturns(errors.New("push failed"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: true,
|
PushOnWrites: true,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -419,7 +394,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setupMock func(*mocks.FakeStagedWriter)
|
setupMock func(*mocks.FakeStagedWriter)
|
||||||
opts repository.CloneOptions
|
opts repository.StageOptions
|
||||||
path string
|
path string
|
||||||
ref string
|
ref string
|
||||||
data []byte
|
data []byte
|
||||||
@@ -435,7 +410,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
|||||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -454,7 +429,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
|||||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||||
mockWriter.PushReturns(nil)
|
mockWriter.PushReturns(nil)
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: true,
|
PushOnWrites: true,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -470,7 +445,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
|||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
// No setup needed as error occurs before writer calls
|
// No setup needed as error occurs before writer calls
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -484,7 +459,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
|||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
mockWriter.BlobExistsReturns(false, errors.New("blob exists check failed"))
|
mockWriter.BlobExistsReturns(false, errors.New("blob exists check failed"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -499,7 +474,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
|||||||
mockWriter.BlobExistsReturns(false, nil)
|
mockWriter.BlobExistsReturns(false, nil)
|
||||||
mockWriter.CreateBlobReturns(hash.Hash{}, errors.New("create failed"))
|
mockWriter.CreateBlobReturns(hash.Hash{}, errors.New("create failed"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -514,7 +489,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
|||||||
mockWriter.BlobExistsReturns(true, nil)
|
mockWriter.BlobExistsReturns(true, nil)
|
||||||
mockWriter.UpdateBlobReturns(hash.Hash{}, errors.New("update failed"))
|
mockWriter.UpdateBlobReturns(hash.Hash{}, errors.New("update failed"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -530,7 +505,7 @@ func TestStagedGitRepository_Write(t *testing.T) {
|
|||||||
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||||
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -570,7 +545,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setupMock func(*mocks.FakeStagedWriter)
|
setupMock func(*mocks.FakeStagedWriter)
|
||||||
opts repository.CloneOptions
|
opts repository.StageOptions
|
||||||
path string
|
path string
|
||||||
ref string
|
ref string
|
||||||
data []byte
|
data []byte
|
||||||
@@ -584,7 +559,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
|||||||
mockWriter.UpdateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
mockWriter.UpdateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -601,7 +576,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
|||||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||||
mockWriter.PushReturns(nil)
|
mockWriter.PushReturns(nil)
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: true,
|
PushOnWrites: true,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -616,7 +591,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
|||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
// No setup needed as error occurs before writer calls
|
// No setup needed as error occurs before writer calls
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -630,7 +605,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
|||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
// No setup needed as error occurs before writer calls
|
// No setup needed as error occurs before writer calls
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "directory/",
|
path: "directory/",
|
||||||
@@ -644,7 +619,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
|||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
mockWriter.UpdateBlobReturns(hash.Hash{}, errors.New("update blob failed"))
|
mockWriter.UpdateBlobReturns(hash.Hash{}, errors.New("update blob failed"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -659,7 +634,7 @@ func TestStagedGitRepository_Update(t *testing.T) {
|
|||||||
mockWriter.UpdateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
mockWriter.UpdateBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||||
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -699,7 +674,7 @@ func TestStagedGitRepository_Delete(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setupMock func(*mocks.FakeStagedWriter)
|
setupMock func(*mocks.FakeStagedWriter)
|
||||||
opts repository.CloneOptions
|
opts repository.StageOptions
|
||||||
path string
|
path string
|
||||||
ref string
|
ref string
|
||||||
message string
|
message string
|
||||||
@@ -712,7 +687,7 @@ func TestStagedGitRepository_Delete(t *testing.T) {
|
|||||||
mockWriter.DeleteBlobReturns(hash.Hash{1, 2, 3}, nil)
|
mockWriter.DeleteBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -728,7 +703,7 @@ func TestStagedGitRepository_Delete(t *testing.T) {
|
|||||||
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
mockWriter.CommitReturns(&nanogit.Commit{}, nil)
|
||||||
mockWriter.PushReturns(nil)
|
mockWriter.PushReturns(nil)
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: true,
|
PushOnWrites: true,
|
||||||
},
|
},
|
||||||
path: "testdir/",
|
path: "testdir/",
|
||||||
@@ -742,7 +717,7 @@ func TestStagedGitRepository_Delete(t *testing.T) {
|
|||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
// No setup needed as error occurs before writer calls
|
// No setup needed as error occurs before writer calls
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -755,7 +730,7 @@ func TestStagedGitRepository_Delete(t *testing.T) {
|
|||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
mockWriter.DeleteBlobReturns(hash.Hash{}, errors.New("delete blob failed"))
|
mockWriter.DeleteBlobReturns(hash.Hash{}, errors.New("delete blob failed"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -769,7 +744,7 @@ func TestStagedGitRepository_Delete(t *testing.T) {
|
|||||||
mockWriter.DeleteBlobReturns(hash.Hash{1, 2, 3}, nil)
|
mockWriter.DeleteBlobReturns(hash.Hash{1, 2, 3}, nil)
|
||||||
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed"))
|
||||||
},
|
},
|
||||||
opts: repository.CloneOptions{
|
opts: repository.StageOptions{
|
||||||
PushOnWrites: false,
|
PushOnWrites: false,
|
||||||
},
|
},
|
||||||
path: "test.yaml",
|
path: "test.yaml",
|
||||||
@@ -808,7 +783,6 @@ func TestStagedGitRepository_Push(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setupMock func(*mocks.FakeStagedWriter)
|
setupMock func(*mocks.FakeStagedWriter)
|
||||||
opts repository.PushOptions
|
|
||||||
wantError error
|
wantError error
|
||||||
expectCalls int
|
expectCalls int
|
||||||
}{
|
}{
|
||||||
@@ -817,7 +791,6 @@ func TestStagedGitRepository_Push(t *testing.T) {
|
|||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
mockWriter.PushReturns(nil)
|
mockWriter.PushReturns(nil)
|
||||||
},
|
},
|
||||||
opts: repository.PushOptions{},
|
|
||||||
wantError: nil,
|
wantError: nil,
|
||||||
expectCalls: 1,
|
expectCalls: 1,
|
||||||
},
|
},
|
||||||
@@ -826,11 +799,6 @@ func TestStagedGitRepository_Push(t *testing.T) {
|
|||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
mockWriter.PushReturns(nil)
|
mockWriter.PushReturns(nil)
|
||||||
},
|
},
|
||||||
opts: repository.PushOptions{
|
|
||||||
BeforeFn: func() error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantError: nil,
|
wantError: nil,
|
||||||
expectCalls: 1,
|
expectCalls: 1,
|
||||||
},
|
},
|
||||||
@@ -839,31 +807,14 @@ func TestStagedGitRepository_Push(t *testing.T) {
|
|||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
mockWriter.PushReturns(nil)
|
mockWriter.PushReturns(nil)
|
||||||
},
|
},
|
||||||
opts: repository.PushOptions{
|
|
||||||
Timeout: time.Second * 5,
|
|
||||||
},
|
|
||||||
wantError: nil,
|
wantError: nil,
|
||||||
expectCalls: 1,
|
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",
|
name: "fails with push error",
|
||||||
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
setupMock: func(mockWriter *mocks.FakeStagedWriter) {
|
||||||
mockWriter.PushReturns(errors.New("push failed"))
|
mockWriter.PushReturns(errors.New("push failed"))
|
||||||
},
|
},
|
||||||
opts: repository.PushOptions{},
|
|
||||||
wantError: errors.New("push failed"),
|
wantError: errors.New("push failed"),
|
||||||
expectCalls: 1,
|
expectCalls: 1,
|
||||||
},
|
},
|
||||||
@@ -874,9 +825,9 @@ func TestStagedGitRepository_Push(t *testing.T) {
|
|||||||
mockWriter := &mocks.FakeStagedWriter{}
|
mockWriter := &mocks.FakeStagedWriter{}
|
||||||
tt.setupMock(mockWriter)
|
tt.setupMock(mockWriter)
|
||||||
|
|
||||||
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, repository.CloneOptions{})
|
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, repository.StageOptions{})
|
||||||
|
|
||||||
err := stagedRepo.Push(context.Background(), tt.opts)
|
err := stagedRepo.Push(context.Background())
|
||||||
|
|
||||||
if tt.wantError != nil {
|
if tt.wantError != nil {
|
||||||
require.EqualError(t, err, tt.wantError.Error())
|
require.EqualError(t, err, tt.wantError.Error())
|
||||||
@@ -892,7 +843,7 @@ func TestStagedGitRepository_Push(t *testing.T) {
|
|||||||
func TestStagedGitRepository_Remove(t *testing.T) {
|
func TestStagedGitRepository_Remove(t *testing.T) {
|
||||||
t.Run("succeeds with remove", func(t *testing.T) {
|
t.Run("succeeds with remove", func(t *testing.T) {
|
||||||
mockWriter := &mocks.FakeStagedWriter{}
|
mockWriter := &mocks.FakeStagedWriter{}
|
||||||
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, repository.CloneOptions{})
|
stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, repository.StageOptions{})
|
||||||
|
|
||||||
err := stagedRepo.Remove(context.Background())
|
err := stagedRepo.Remove(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -904,10 +855,10 @@ func TestStagedGitRepository_Remove(t *testing.T) {
|
|||||||
|
|
||||||
func createTestStagedRepository(mockClient *mocks.FakeClient) *stagedGitRepository {
|
func createTestStagedRepository(mockClient *mocks.FakeClient) *stagedGitRepository {
|
||||||
mockWriter := &mocks.FakeStagedWriter{}
|
mockWriter := &mocks.FakeStagedWriter{}
|
||||||
return createTestStagedRepositoryWithWriter(mockWriter, repository.CloneOptions{}, mockClient)
|
return createTestStagedRepositoryWithWriter(mockWriter, repository.StageOptions{}, mockClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestStagedRepositoryWithWriter(mockWriter *mocks.FakeStagedWriter, opts repository.CloneOptions, mockClient ...*mocks.FakeClient) *stagedGitRepository {
|
func createTestStagedRepositoryWithWriter(mockWriter *mocks.FakeStagedWriter, opts repository.StageOptions, mockClient ...*mocks.FakeClient) *stagedGitRepository {
|
||||||
var client nanogit.Client
|
var client nanogit.Client
|
||||||
if len(mockClient) > 0 {
|
if len(mockClient) > 0 {
|
||||||
client = mockClient[0]
|
client = mockClient[0]
|
||||||
@@ -1,683 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/logging"
|
|
||||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
|
||||||
pgh "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Make sure all public functions of this struct call the (*githubRepository).logger function, to ensure the GH repo details are included.
|
|
||||||
type githubRepository struct {
|
|
||||||
config *provisioning.Repository
|
|
||||||
gh pgh.Client // assumes github.com base URL
|
|
||||||
secrets secrets.Service
|
|
||||||
|
|
||||||
owner string
|
|
||||||
repo string
|
|
||||||
|
|
||||||
cloneFn CloneFn
|
|
||||||
}
|
|
||||||
|
|
||||||
// GithubRepository is an interface that combines all repository capabilities
|
|
||||||
// needed for GitHub repositories.
|
|
||||||
|
|
||||||
//go:generate mockery --name GithubRepository --structname MockGithubRepository --inpackage --filename github_repository_mock.go --with-expecter
|
|
||||||
type GithubRepository interface {
|
|
||||||
Repository
|
|
||||||
Versioned
|
|
||||||
Writer
|
|
||||||
Reader
|
|
||||||
RepositoryWithURLs
|
|
||||||
ClonableRepository
|
|
||||||
Owner() string
|
|
||||||
Repo() string
|
|
||||||
Client() pgh.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGitHub(
|
|
||||||
ctx context.Context,
|
|
||||||
config *provisioning.Repository,
|
|
||||||
factory *pgh.Factory,
|
|
||||||
secrets secrets.Service,
|
|
||||||
cloneFn CloneFn,
|
|
||||||
) (GithubRepository, error) {
|
|
||||||
owner, repo, err := ParseOwnerRepoGithub(config.Spec.GitHub.URL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse owner and repo: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
token := config.Spec.GitHub.Token
|
|
||||||
if token == "" {
|
|
||||||
decrypted, err := secrets.Decrypt(ctx, config.Spec.GitHub.EncryptedToken)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("decrypt token: %w", err)
|
|
||||||
}
|
|
||||||
token = string(decrypted)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &githubRepository{
|
|
||||||
config: config,
|
|
||||||
gh: factory.New(ctx, token), // TODO, baseURL from config
|
|
||||||
secrets: secrets,
|
|
||||||
owner: owner,
|
|
||||||
repo: repo,
|
|
||||||
cloneFn: cloneFn,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Config() *provisioning.Repository {
|
|
||||||
return r.config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Owner() string {
|
|
||||||
return r.owner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Repo() string {
|
|
||||||
return r.repo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Client() pgh.Client {
|
|
||||||
return r.gh
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate implements provisioning.Repository.
|
|
||||||
func (r *githubRepository) Validate() (list field.ErrorList) {
|
|
||||||
gh := r.config.Spec.GitHub
|
|
||||||
if gh == nil {
|
|
||||||
list = append(list, field.Required(field.NewPath("spec", "github"), "a github config is required"))
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
if gh.URL == "" {
|
|
||||||
list = append(list, field.Required(field.NewPath("spec", "github", "url"), "a github url is required"))
|
|
||||||
} else {
|
|
||||||
_, _, err := ParseOwnerRepoGithub(gh.URL)
|
|
||||||
if err != nil {
|
|
||||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, err.Error()))
|
|
||||||
} else if !strings.HasPrefix(gh.URL, "https://github.com/") {
|
|
||||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, "URL must start with https://github.com/"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if gh.Branch == "" {
|
|
||||||
list = append(list, field.Required(field.NewPath("spec", "github", "branch"), "a github branch is required"))
|
|
||||||
} else if !IsValidGitBranchName(gh.Branch) {
|
|
||||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "branch"), gh.Branch, "invalid branch name"))
|
|
||||||
}
|
|
||||||
// TODO: Use two fields for token
|
|
||||||
if gh.Token == "" && len(gh.EncryptedToken) == 0 {
|
|
||||||
list = append(list, field.Required(field.NewPath("spec", "github", "token"), "a github access token is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := safepath.IsSafe(gh.Path); err != nil {
|
|
||||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "prefix"), gh.Path, err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if safepath.IsAbs(gh.Path) {
|
|
||||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "prefix"), gh.Path, "path must be relative"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseOwnerRepoGithub(giturl string) (owner string, repo string, err error) {
|
|
||||||
parsed, e := url.Parse(strings.TrimSuffix(giturl, ".git"))
|
|
||||||
if e != nil {
|
|
||||||
err = e
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parts := strings.Split(parsed.Path, "/")
|
|
||||||
if len(parts) < 3 {
|
|
||||||
err = fmt.Errorf("unable to parse repo+owner from url")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return parts[1], parts[2], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test implements provisioning.Repository.
|
|
||||||
func (r *githubRepository) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
|
||||||
if err := r.gh.IsAuthenticated(ctx); err != nil {
|
|
||||||
return &provisioning.TestResults{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
Success: false,
|
|
||||||
Errors: []provisioning.ErrorDetails{{
|
|
||||||
Type: metav1.CauseTypeFieldValueInvalid,
|
|
||||||
Field: field.NewPath("spec", "github", "token").String(),
|
|
||||||
Detail: err.Error(),
|
|
||||||
}}}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
url := r.config.Spec.GitHub.URL
|
|
||||||
owner, repo, err := ParseOwnerRepoGithub(url)
|
|
||||||
if err != nil {
|
|
||||||
return fromFieldError(field.Invalid(
|
|
||||||
field.NewPath("spec", "github", "url"), url, err.Error())), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: check token permissions
|
|
||||||
ok, err := r.gh.RepoExists(ctx, owner, repo)
|
|
||||||
if err != nil {
|
|
||||||
return fromFieldError(field.Invalid(
|
|
||||||
field.NewPath("spec", "github", "url"), url, err.Error())), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return fromFieldError(field.NotFound(
|
|
||||||
field.NewPath("spec", "github", "url"), url)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
branch := r.config.Spec.GitHub.Branch
|
|
||||||
ok, err = r.gh.BranchExists(ctx, r.owner, r.repo, branch)
|
|
||||||
if err != nil {
|
|
||||||
return fromFieldError(field.Invalid(
|
|
||||||
field.NewPath("spec", "github", "branch"), branch, err.Error())), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return fromFieldError(field.NotFound(
|
|
||||||
field.NewPath("spec", "github", "branch"), branch)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &provisioning.TestResults{
|
|
||||||
Code: http.StatusOK,
|
|
||||||
Success: true,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadResource implements provisioning.Repository.
|
|
||||||
func (r *githubRepository) Read(ctx context.Context, filePath, ref string) (*FileInfo, error) {
|
|
||||||
if ref == "" {
|
|
||||||
ref = r.config.Spec.GitHub.Branch
|
|
||||||
}
|
|
||||||
|
|
||||||
finalPath := safepath.Join(r.config.Spec.GitHub.Path, filePath)
|
|
||||||
content, dirContent, err := r.gh.GetContents(ctx, r.owner, r.repo, finalPath, ref)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, pgh.ErrResourceNotFound) {
|
|
||||||
return nil, ErrFileNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("get contents: %w", err)
|
|
||||||
}
|
|
||||||
if dirContent != nil {
|
|
||||||
return &FileInfo{
|
|
||||||
Path: filePath,
|
|
||||||
Ref: ref,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := content.GetFileContent()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("get content: %w", err)
|
|
||||||
}
|
|
||||||
return &FileInfo{
|
|
||||||
Path: filePath,
|
|
||||||
Ref: ref,
|
|
||||||
Data: []byte(data),
|
|
||||||
Hash: content.GetSHA(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) {
|
|
||||||
if ref == "" {
|
|
||||||
ref = r.config.Spec.GitHub.Branch
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, _ = r.logger(ctx, ref)
|
|
||||||
tree, truncated, err := r.gh.GetTree(ctx, r.owner, r.repo, r.config.Spec.GitHub.Path, ref, true)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, pgh.ErrResourceNotFound) {
|
|
||||||
return nil, &apierrors.StatusError{
|
|
||||||
ErrStatus: metav1.Status{
|
|
||||||
Message: fmt.Sprintf("tree not found; ref=%s", ref),
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("get tree: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if truncated {
|
|
||||||
return nil, fmt.Errorf("tree truncated")
|
|
||||||
}
|
|
||||||
|
|
||||||
entries := make([]FileTreeEntry, 0, len(tree))
|
|
||||||
for _, entry := range tree {
|
|
||||||
isBlob := !entry.IsDirectory()
|
|
||||||
// FIXME: this we could potentially do somewhere else on in a different way
|
|
||||||
filePath := entry.GetPath()
|
|
||||||
if !isBlob && !safepath.IsDir(filePath) {
|
|
||||||
filePath = filePath + "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
converted := FileTreeEntry{
|
|
||||||
Path: filePath,
|
|
||||||
Size: entry.GetSize(),
|
|
||||||
Hash: entry.GetSHA(),
|
|
||||||
Blob: !entry.IsDirectory(),
|
|
||||||
}
|
|
||||||
entries = append(entries, converted)
|
|
||||||
}
|
|
||||||
return entries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Create(ctx context.Context, path, ref string, data []byte, comment string) error {
|
|
||||||
if ref == "" {
|
|
||||||
ref = r.config.Spec.GitHub.Branch
|
|
||||||
}
|
|
||||||
ctx, _ = r.logger(ctx, ref)
|
|
||||||
|
|
||||||
if err := r.ensureBranchExists(ctx, ref); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
finalPath := safepath.Join(r.config.Spec.GitHub.Path, path)
|
|
||||||
|
|
||||||
// Create .keep file if it is a directory
|
|
||||||
if safepath.IsDir(finalPath) {
|
|
||||||
if data != nil {
|
|
||||||
return apierrors.NewBadRequest("data cannot be provided for a directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
finalPath = safepath.Join(finalPath, ".keep")
|
|
||||||
data = []byte{}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := r.gh.CreateFile(ctx, r.owner, r.repo, finalPath, ref, comment, data)
|
|
||||||
if errors.Is(err, pgh.ErrResourceAlreadyExists) {
|
|
||||||
return &apierrors.StatusError{
|
|
||||||
ErrStatus: metav1.Status{
|
|
||||||
Message: "file already exists",
|
|
||||||
Code: http.StatusConflict,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Update(ctx context.Context, path, ref string, data []byte, comment string) error {
|
|
||||||
if ref == "" {
|
|
||||||
ref = r.config.Spec.GitHub.Branch
|
|
||||||
}
|
|
||||||
ctx, _ = r.logger(ctx, ref)
|
|
||||||
|
|
||||||
if err := r.ensureBranchExists(ctx, ref); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
finalPath := safepath.Join(r.config.Spec.GitHub.Path, path)
|
|
||||||
file, _, err := r.gh.GetContents(ctx, r.owner, r.repo, finalPath, ref)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, pgh.ErrResourceNotFound) {
|
|
||||||
return &apierrors.StatusError{
|
|
||||||
ErrStatus: metav1.Status{
|
|
||||||
Message: "file not found",
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("get content before file update: %w", err)
|
|
||||||
}
|
|
||||||
if file.IsDirectory() {
|
|
||||||
return apierrors.NewBadRequest("cannot update a directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.gh.UpdateFile(ctx, r.owner, r.repo, finalPath, ref, comment, file.GetSHA(), data); err != nil {
|
|
||||||
return fmt.Errorf("update file: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
|
|
||||||
if ref == "" {
|
|
||||||
ref = r.config.Spec.GitHub.Branch
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, _ = r.logger(ctx, ref)
|
|
||||||
_, err := r.Read(ctx, path, ref)
|
|
||||||
if err != nil && !(errors.Is(err, ErrFileNotFound)) {
|
|
||||||
return fmt.Errorf("check if file exists before writing: %w", err)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
return r.Update(ctx, path, ref, data, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.Create(ctx, path, ref, data, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Delete(ctx context.Context, path, ref, comment string) error {
|
|
||||||
if ref == "" {
|
|
||||||
ref = r.config.Spec.GitHub.Branch
|
|
||||||
}
|
|
||||||
ctx, _ = r.logger(ctx, ref)
|
|
||||||
|
|
||||||
if err := r.ensureBranchExists(ctx, ref); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: should add some protection against deleting the root directory?
|
|
||||||
|
|
||||||
// Inside deleteRecursively, all paths are relative to the root of the repository
|
|
||||||
// so we need to prepend the prefix there but only here.
|
|
||||||
finalPath := safepath.Join(r.config.Spec.GitHub.Path, path)
|
|
||||||
|
|
||||||
return r.deleteRecursively(ctx, finalPath, ref, comment)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) deleteRecursively(ctx context.Context, path, ref, comment string) error {
|
|
||||||
file, contents, err := r.gh.GetContents(ctx, r.owner, r.repo, path, ref)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, pgh.ErrResourceNotFound) {
|
|
||||||
return ErrFileNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("find file to delete: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if file != nil && !file.IsDirectory() {
|
|
||||||
return r.gh.DeleteFile(ctx, r.owner, r.repo, path, ref, comment, file.GetSHA())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range contents {
|
|
||||||
p := c.GetPath()
|
|
||||||
if c.IsDirectory() {
|
|
||||||
if err := r.deleteRecursively(ctx, p, ref, comment); err != nil {
|
|
||||||
return fmt.Errorf("delete directory recursively: %w", err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.gh.DeleteFile(ctx, r.owner, r.repo, p, ref, comment, c.GetSHA()); err != nil {
|
|
||||||
return fmt.Errorf("delete file: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) History(ctx context.Context, path, ref string) ([]provisioning.HistoryItem, error) {
|
|
||||||
if ref == "" {
|
|
||||||
ref = r.config.Spec.GitHub.Branch
|
|
||||||
}
|
|
||||||
ctx, _ = r.logger(ctx, ref)
|
|
||||||
|
|
||||||
finalPath := safepath.Join(r.config.Spec.GitHub.Path, path)
|
|
||||||
commits, err := r.gh.Commits(ctx, r.owner, r.repo, finalPath, ref)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, pgh.ErrResourceNotFound) {
|
|
||||||
return nil, ErrFileNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("get commits: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := make([]provisioning.HistoryItem, 0, len(commits))
|
|
||||||
for _, commit := range commits {
|
|
||||||
authors := make([]provisioning.Author, 0)
|
|
||||||
if commit.Author != nil {
|
|
||||||
authors = append(authors, provisioning.Author{
|
|
||||||
Name: commit.Author.Name,
|
|
||||||
Username: commit.Author.Username,
|
|
||||||
AvatarURL: commit.Author.AvatarURL,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if commit.Committer != nil && commit.Author != nil && commit.Author.Name != commit.Committer.Name {
|
|
||||||
authors = append(authors, provisioning.Author{
|
|
||||||
Name: commit.Committer.Name,
|
|
||||||
Username: commit.Committer.Username,
|
|
||||||
AvatarURL: commit.Committer.AvatarURL,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = append(ret, provisioning.HistoryItem{
|
|
||||||
Ref: commit.Ref,
|
|
||||||
Message: commit.Message,
|
|
||||||
Authors: authors,
|
|
||||||
CreatedAt: commit.CreatedAt.UnixMilli(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// basicGitBranchNameRegex is a regular expression to validate a git branch name
|
|
||||||
// it does not cover all cases as positive lookaheads are not supported in Go's regexp
|
|
||||||
var basicGitBranchNameRegex = regexp.MustCompile(`^[a-zA-Z0-9\-\_\/\.]+$`)
|
|
||||||
|
|
||||||
// IsValidGitBranchName checks if a branch name is valid.
|
|
||||||
// It uses the following regexp `^[a-zA-Z0-9\-\_\/\.]+$` to validate the branch name with some additional checks that must satisfy the following rules:
|
|
||||||
// 1. The branch name must have at least one character and must not be empty.
|
|
||||||
// 2. The branch name cannot start with `/` or end with `/`, `.`, or whitespace.
|
|
||||||
// 3. The branch name cannot contain consecutive slashes (`//`).
|
|
||||||
// 4. The branch name cannot contain consecutive dots (`..`).
|
|
||||||
// 5. The branch name cannot contain `@{`.
|
|
||||||
// 6. The branch name cannot include the following characters: `~`, `^`, `:`, `?`, `*`, `[`, `\`, or `]`.
|
|
||||||
func IsValidGitBranchName(branch string) bool {
|
|
||||||
if !basicGitBranchNameRegex.MatchString(branch) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional checks for invalid patterns
|
|
||||||
if strings.HasPrefix(branch, "/") || strings.HasSuffix(branch, "/") ||
|
|
||||||
strings.HasSuffix(branch, ".") || strings.Contains(branch, "..") ||
|
|
||||||
strings.Contains(branch, "//") || strings.HasSuffix(branch, ".lock") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) ensureBranchExists(ctx context.Context, branchName string) error {
|
|
||||||
if !IsValidGitBranchName(branchName) {
|
|
||||||
return &apierrors.StatusError{
|
|
||||||
ErrStatus: metav1.Status{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
Message: "invalid branch name",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ok, err := r.gh.BranchExists(ctx, r.owner, r.repo, branchName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("check branch exists: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
logging.FromContext(ctx).Info("branch already exists", "branch", branchName)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
srcBranch := r.config.Spec.GitHub.Branch
|
|
||||||
if err := r.gh.CreateBranch(ctx, r.owner, r.repo, srcBranch, branchName); err != nil {
|
|
||||||
if errors.Is(err, pgh.ErrResourceAlreadyExists) {
|
|
||||||
return &apierrors.StatusError{
|
|
||||||
ErrStatus: metav1.Status{
|
|
||||||
Code: http.StatusConflict,
|
|
||||||
Message: "branch already exists",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("create branch: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) LatestRef(ctx context.Context) (string, error) {
|
|
||||||
ctx, _ = r.logger(ctx, "")
|
|
||||||
branch, err := r.gh.GetBranch(ctx, r.owner, r.repo, r.Config().Spec.GitHub.Branch)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("get branch: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return branch.Sha, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) CompareFiles(ctx context.Context, base, ref string) ([]VersionedFileChange, error) {
|
|
||||||
if ref == "" {
|
|
||||||
var err error
|
|
||||||
ref, err = r.LatestRef(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("get latest ref: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx, logger := r.logger(ctx, ref)
|
|
||||||
|
|
||||||
files, err := r.gh.CompareCommits(ctx, r.owner, r.repo, base, ref)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("compare commits: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
changes := make([]VersionedFileChange, 0)
|
|
||||||
for _, f := range files {
|
|
||||||
// reference: https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#get-a-commit
|
|
||||||
switch f.GetStatus() {
|
|
||||||
case "added", "copied":
|
|
||||||
currentPath, err := safepath.RelativeTo(f.GetFilename(), r.config.Spec.GitHub.Path)
|
|
||||||
if err != nil {
|
|
||||||
// do nothing as it's outside of configured path
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
changes = append(changes, VersionedFileChange{
|
|
||||||
Path: currentPath,
|
|
||||||
Ref: ref,
|
|
||||||
Action: FileActionCreated,
|
|
||||||
})
|
|
||||||
case "modified", "changed":
|
|
||||||
currentPath, err := safepath.RelativeTo(f.GetFilename(), r.config.Spec.GitHub.Path)
|
|
||||||
if err != nil {
|
|
||||||
// do nothing as it's outside of configured path
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
changes = append(changes, VersionedFileChange{
|
|
||||||
Path: currentPath,
|
|
||||||
Ref: ref,
|
|
||||||
Action: FileActionUpdated,
|
|
||||||
})
|
|
||||||
case "renamed":
|
|
||||||
previousPath, previousErr := safepath.RelativeTo(f.GetPreviousFilename(), r.config.Spec.GitHub.Path)
|
|
||||||
currentPath, currentErr := safepath.RelativeTo(f.GetFilename(), r.config.Spec.GitHub.Path)
|
|
||||||
|
|
||||||
// Handle all possible combinations of path validation results:
|
|
||||||
// 1. Both paths outside configured path, do nothing
|
|
||||||
// 2. Both paths inside configured path, rename
|
|
||||||
// 3. Moving out of configured path, delete previous file
|
|
||||||
// 4. Moving into configured path, create new file
|
|
||||||
switch {
|
|
||||||
case previousErr != nil && currentErr != nil:
|
|
||||||
// do nothing as it's outside of configured path
|
|
||||||
case previousErr == nil && currentErr == nil:
|
|
||||||
changes = append(changes, VersionedFileChange{
|
|
||||||
Path: currentPath,
|
|
||||||
PreviousPath: previousPath,
|
|
||||||
Ref: ref,
|
|
||||||
PreviousRef: base,
|
|
||||||
Action: FileActionRenamed,
|
|
||||||
})
|
|
||||||
case previousErr == nil && currentErr != nil:
|
|
||||||
changes = append(changes, VersionedFileChange{
|
|
||||||
Path: previousPath,
|
|
||||||
Ref: base,
|
|
||||||
Action: FileActionDeleted,
|
|
||||||
})
|
|
||||||
case previousErr != nil && currentErr == nil:
|
|
||||||
changes = append(changes, VersionedFileChange{
|
|
||||||
Path: currentPath,
|
|
||||||
Ref: ref,
|
|
||||||
Action: FileActionCreated,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case "removed":
|
|
||||||
currentPath, err := safepath.RelativeTo(f.GetFilename(), r.config.Spec.GitHub.Path)
|
|
||||||
if err != nil {
|
|
||||||
// do nothing as it's outside of configured path
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
changes = append(changes, VersionedFileChange{
|
|
||||||
Ref: ref,
|
|
||||||
PreviousRef: base,
|
|
||||||
Path: currentPath,
|
|
||||||
PreviousPath: currentPath,
|
|
||||||
Action: FileActionDeleted,
|
|
||||||
})
|
|
||||||
case "unchanged":
|
|
||||||
// do nothing
|
|
||||||
default:
|
|
||||||
logger.Error("ignore unhandled file", "file", f.GetFilename(), "status", f.GetStatus())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return changes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceURLs implements RepositoryWithURLs.
|
|
||||||
func (r *githubRepository) ResourceURLs(ctx context.Context, file *FileInfo) (*provisioning.ResourceURLs, error) {
|
|
||||||
cfg := r.config.Spec.GitHub
|
|
||||||
if file.Path == "" || cfg == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ref := file.Ref
|
|
||||||
if ref == "" {
|
|
||||||
ref = cfg.Branch
|
|
||||||
}
|
|
||||||
|
|
||||||
urls := &provisioning.ResourceURLs{
|
|
||||||
RepositoryURL: cfg.URL,
|
|
||||||
SourceURL: fmt.Sprintf("%s/blob/%s/%s", cfg.URL, ref, file.Path),
|
|
||||||
}
|
|
||||||
|
|
||||||
if ref != cfg.Branch {
|
|
||||||
urls.CompareURL = fmt.Sprintf("%s/compare/%s...%s", cfg.URL, cfg.Branch, ref)
|
|
||||||
|
|
||||||
// Create a new pull request
|
|
||||||
urls.NewPullRequestURL = fmt.Sprintf("%s?quick_pull=1&labels=grafana", urls.CompareURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
return urls, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Clone(ctx context.Context, opts CloneOptions) (ClonedRepository, error) {
|
|
||||||
return r.cloneFn(ctx, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) logger(ctx context.Context, ref string) (context.Context, logging.Logger) {
|
|
||||||
logger := logging.FromContext(ctx)
|
|
||||||
|
|
||||||
type containsGh int
|
|
||||||
var containsGhKey containsGh
|
|
||||||
if ctx.Value(containsGhKey) != nil {
|
|
||||||
return ctx, logging.FromContext(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ref == "" {
|
|
||||||
ref = r.config.Spec.GitHub.Branch
|
|
||||||
}
|
|
||||||
logger = logger.With(slog.Group("github_repository", "owner", r.owner, "name", r.repo, "ref", ref))
|
|
||||||
ctx = logging.Context(ctx, logger)
|
|
||||||
// We want to ensure we don't add multiple github_repository keys. With doesn't deduplicate the keys...
|
|
||||||
ctx = context.WithValue(ctx, containsGhKey, true)
|
|
||||||
return ctx, logger
|
|
||||||
}
|
|
||||||
@@ -7,127 +7,34 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-github/v70/github"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// API errors that we need to convey after parsing real GH errors (or faking them).
|
// API errors that we need to convey after parsing real GH errors (or faking them).
|
||||||
var (
|
var (
|
||||||
ErrResourceAlreadyExists = errors.New("the resource already exists")
|
ErrResourceNotFound = errors.New("the resource does not exist")
|
||||||
ErrResourceNotFound = errors.New("the resource does not exist")
|
|
||||||
ErrMismatchedHash = errors.New("the update cannot be applied because the expected and actual hashes are unequal")
|
|
||||||
ErrNoSecret = errors.New("new webhooks must have a secret")
|
|
||||||
//lint:ignore ST1005 this is not punctuation
|
//lint:ignore ST1005 this is not punctuation
|
||||||
ErrPathTraversalDisallowed = errors.New("the path contained ..") //nolint:staticcheck
|
ErrServiceUnavailable = apierrors.NewServiceUnavailable("github is unavailable")
|
||||||
ErrServiceUnavailable = apierrors.NewServiceUnavailable("github is unavailable")
|
ErrTooManyItems = errors.New("maximum number of items exceeded")
|
||||||
ErrFileTooLarge = errors.New("file exceeds maximum allowed size")
|
|
||||||
ErrTooManyItems = errors.New("maximum number of items exceeded")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MaxFileSize maximum file size limit (10MB)
|
|
||||||
const MaxFileSize = 10 * 1024 * 1024 // 10MB in bytes
|
|
||||||
|
|
||||||
type ErrRateLimited = github.RateLimitError
|
|
||||||
|
|
||||||
//go:generate mockery --name Client --structname MockClient --inpackage --filename mock_client.go --with-expecter
|
//go:generate mockery --name Client --structname MockClient --inpackage --filename mock_client.go --with-expecter
|
||||||
type Client interface {
|
type Client interface {
|
||||||
// IsAuthenticated checks if the client is authenticated.
|
// Commits
|
||||||
IsAuthenticated(ctx context.Context) error
|
|
||||||
|
|
||||||
// GetContents returns the metadata and content of a file or directory.
|
|
||||||
// When a file is checked, the first returned value will have a value. For a directory, the second will. The other value is always nil.
|
|
||||||
// If an error occurs, the returned values may or may not be nil.
|
|
||||||
//
|
|
||||||
// If ".." appears in the "path", this method will return an error.
|
|
||||||
GetContents(ctx context.Context, owner, repository, path, ref string) (fileContents RepositoryContent, dirContents []RepositoryContent, err error)
|
|
||||||
|
|
||||||
// GetTree returns the Git tree in the repository.
|
|
||||||
// When recursive is given, subtrees are mapped into the returned array.
|
|
||||||
// When basePath is given, only trees under it are given. The results do not include this path in their names.
|
|
||||||
//
|
|
||||||
// The truncated bool will be set to true if the tree is larger than 7 MB or 100 000 entries.
|
|
||||||
// When truncated is true, you may wish to read each subtree manually instead.
|
|
||||||
GetTree(ctx context.Context, owner, repository, basePath, ref string, recursive bool) (entries []RepositoryContent, truncated bool, err error)
|
|
||||||
|
|
||||||
// CreateFile creates a new file in the repository under the given path.
|
|
||||||
// The file is created on the branch given.
|
|
||||||
// The message given is the commit message. If none is given, an appropriate default is used.
|
|
||||||
// The content is what the file should contain. An empty slice is valid, though often not very useful.
|
|
||||||
//
|
|
||||||
// If ".." appears in the "path", this method will return an error.
|
|
||||||
CreateFile(ctx context.Context, owner, repository, path, branch, message string, content []byte) error
|
|
||||||
|
|
||||||
// UpdateFile updates a file in the repository under the given path.
|
|
||||||
// The file is updated on the branch given.
|
|
||||||
// The message given is the commit message. If none is given, an appropriate default is used.
|
|
||||||
// The content is what the file should contain. An empty slice is valid, though often not very useful.
|
|
||||||
// If the path does not exist, an error is returned.
|
|
||||||
// The hash given must be the SHA hash of the file contents. Calling GetContents in advance is an easy way of handling this.
|
|
||||||
//
|
|
||||||
// If ".." appears in the "path", this method will return an error.
|
|
||||||
UpdateFile(ctx context.Context, owner, repository, path, branch, message, hash string, content []byte) error
|
|
||||||
|
|
||||||
// DeleteFile deletes a file in the repository under the given path.
|
|
||||||
// The file is deleted from the branch given.
|
|
||||||
// The message given is the commit message. If none is given, an appropriate default is used.
|
|
||||||
// If the path does not exist, an error is returned.
|
|
||||||
// The hash given must be the SHA hash of the file contents. Calling GetContents in advance is an easy way of handling this.
|
|
||||||
//
|
|
||||||
// If ".." appears in the "path", this method will return an error.
|
|
||||||
DeleteFile(ctx context.Context, owner, repository, path, branch, message, hash string) error
|
|
||||||
|
|
||||||
// Commits returns the commits for the given path
|
|
||||||
Commits(ctx context.Context, owner, repository, path, branch string) ([]Commit, error)
|
Commits(ctx context.Context, owner, repository, path, branch string) ([]Commit, error)
|
||||||
|
|
||||||
// CompareCommits returns the changes between two commits.
|
// Webhooks
|
||||||
CompareCommits(ctx context.Context, owner, repository, base, head string) ([]CommitFile, error)
|
|
||||||
|
|
||||||
// RepoExists checks if a repository exists.
|
|
||||||
RepoExists(ctx context.Context, owner, repository string) (bool, error)
|
|
||||||
|
|
||||||
// CreateBranch creates a new branch in the repository.
|
|
||||||
CreateBranch(ctx context.Context, owner, repository, sourceBranch, branchName string) error
|
|
||||||
// BranchExists checks if a branch exists in the repository.
|
|
||||||
BranchExists(ctx context.Context, owner, repository, branchName string) (bool, error)
|
|
||||||
// GetBranch returns the branch of the repository.
|
|
||||||
GetBranch(ctx context.Context, owner, repository, branchName string) (Branch, error)
|
|
||||||
|
|
||||||
ListWebhooks(ctx context.Context, owner, repository string) ([]WebhookConfig, error)
|
ListWebhooks(ctx context.Context, owner, repository string) ([]WebhookConfig, error)
|
||||||
CreateWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) (WebhookConfig, error)
|
CreateWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) (WebhookConfig, error)
|
||||||
GetWebhook(ctx context.Context, owner, repository string, webhookID int64) (WebhookConfig, error)
|
GetWebhook(ctx context.Context, owner, repository string, webhookID int64) (WebhookConfig, error)
|
||||||
DeleteWebhook(ctx context.Context, owner, repository string, webhookID int64) error
|
DeleteWebhook(ctx context.Context, owner, repository string, webhookID int64) error
|
||||||
EditWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) error
|
EditWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) error
|
||||||
|
|
||||||
|
// Pull requests
|
||||||
ListPullRequestFiles(ctx context.Context, owner, repository string, number int) ([]CommitFile, error)
|
ListPullRequestFiles(ctx context.Context, owner, repository string, number int) ([]CommitFile, error)
|
||||||
CreatePullRequestComment(ctx context.Context, owner, repository string, number int, body string) error
|
CreatePullRequestComment(ctx context.Context, owner, repository string, number int, body string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate mockery --name RepositoryContent --structname MockRepositoryContent --inpackage --filename mock_repository_content.go --with-expecter
|
|
||||||
type RepositoryContent interface {
|
|
||||||
// Returns true if this is a directory, false if it is a file.
|
|
||||||
IsDirectory() bool
|
|
||||||
// Returns the contents of the file. Decoding happens if necessary.
|
|
||||||
// Returns an error if the content represents a directory.
|
|
||||||
GetFileContent() (string, error)
|
|
||||||
// Returns true if this is a symlink.
|
|
||||||
// If true, GetPath returns the path where this symlink leads.
|
|
||||||
IsSymlink() bool
|
|
||||||
// Returns the full path from the root of the repository.
|
|
||||||
// This has no leading or trailing slashes.
|
|
||||||
// The path only uses '/' for directories. You can use the 'path' package to interact with these.
|
|
||||||
GetPath() string
|
|
||||||
// Get the SHA hash. This is usually a SHA-256, but may also be SHA-512.
|
|
||||||
// Directories have SHA hashes, too (TODO: how is this calculated?).
|
|
||||||
GetSHA() string
|
|
||||||
// The size of the file. Not necessarily non-zero, even if the file is supposed to be non-zero.
|
|
||||||
GetSize() int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type Branch struct {
|
|
||||||
Name string
|
|
||||||
Sha string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommitAuthor struct {
|
type CommitAuthor struct {
|
||||||
Name string
|
Name string
|
||||||
Username string
|
Username string
|
||||||
@@ -150,20 +57,6 @@ type CommitFile interface {
|
|||||||
GetStatus() string
|
GetStatus() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileComment struct {
|
|
||||||
Content string
|
|
||||||
Path string
|
|
||||||
Position int
|
|
||||||
Ref string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateFileOptions struct {
|
|
||||||
// The message of the commit. May be empty, in which case a default value is entered.
|
|
||||||
Message string
|
|
||||||
// The content of the file to write, unencoded.
|
|
||||||
Content []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebhookConfig struct {
|
type WebhookConfig struct {
|
||||||
// The ID of the webhook.
|
// The ID of the webhook.
|
||||||
// Can be 0 on creation.
|
// Can be 0 on creation.
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ func (r *Factory) New(ctx context.Context, ghToken string) Client {
|
|||||||
tokenSrc := oauth2.StaticTokenSource(
|
tokenSrc := oauth2.StaticTokenSource(
|
||||||
&oauth2.Token{AccessToken: ghToken},
|
&oauth2.Token{AccessToken: ghToken},
|
||||||
)
|
)
|
||||||
tokenClient := oauth2.NewClient(ctx, tokenSrc)
|
|
||||||
return NewClient(github.NewClient(tokenClient))
|
if len(ghToken) == 0 {
|
||||||
|
tokenClient := oauth2.NewClient(ctx, tokenSrc)
|
||||||
|
return NewClient(github.NewClient(tokenClient))
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewClient(github.NewClient(&http.Client{}))
|
||||||
}
|
}
|
||||||
|
|||||||
+98
-98
@@ -1,14 +1,14 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
package repository
|
package github
|
||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
|
|
||||||
github "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
mock "github.com/stretchr/testify/mock"
|
||||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
|
|
||||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||||
)
|
)
|
||||||
@@ -27,19 +27,19 @@ func (_m *MockGithubRepository) EXPECT() *MockGithubRepository_Expecter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Client provides a mock function with no fields
|
// Client provides a mock function with no fields
|
||||||
func (_m *MockGithubRepository) Client() github.Client {
|
func (_m *MockGithubRepository) Client() Client {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for Client")
|
panic("no return value specified for Client")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 github.Client
|
var r0 Client
|
||||||
if rf, ok := ret.Get(0).(func() github.Client); ok {
|
if rf, ok := ret.Get(0).(func() Client); ok {
|
||||||
r0 = rf()
|
r0 = rf()
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(github.Client)
|
r0 = ret.Get(0).(Client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,93 +63,34 @@ func (_c *MockGithubRepository_Client_Call) Run(run func()) *MockGithubRepositor
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGithubRepository_Client_Call) Return(_a0 github.Client) *MockGithubRepository_Client_Call {
|
func (_c *MockGithubRepository_Client_Call) Return(_a0 Client) *MockGithubRepository_Client_Call {
|
||||||
_c.Call.Return(_a0)
|
_c.Call.Return(_a0)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGithubRepository_Client_Call) RunAndReturn(run func() github.Client) *MockGithubRepository_Client_Call {
|
func (_c *MockGithubRepository_Client_Call) RunAndReturn(run func() 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)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompareFiles provides a mock function with given fields: ctx, base, ref
|
// CompareFiles provides a mock function with given fields: ctx, base, ref
|
||||||
func (_m *MockGithubRepository) CompareFiles(ctx context.Context, base string, ref string) ([]VersionedFileChange, error) {
|
func (_m *MockGithubRepository) CompareFiles(ctx context.Context, base string, ref string) ([]repository.VersionedFileChange, error) {
|
||||||
ret := _m.Called(ctx, base, ref)
|
ret := _m.Called(ctx, base, ref)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for CompareFiles")
|
panic("no return value specified for CompareFiles")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 []VersionedFileChange
|
var r0 []repository.VersionedFileChange
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]VersionedFileChange, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]repository.VersionedFileChange, error)); ok {
|
||||||
return rf(ctx, base, ref)
|
return rf(ctx, base, ref)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []VersionedFileChange); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, string) []repository.VersionedFileChange); ok {
|
||||||
r0 = rf(ctx, base, ref)
|
r0 = rf(ctx, base, ref)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).([]VersionedFileChange)
|
r0 = ret.Get(0).([]repository.VersionedFileChange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,12 +123,12 @@ func (_c *MockGithubRepository_CompareFiles_Call) Run(run func(ctx context.Conte
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGithubRepository_CompareFiles_Call) Return(_a0 []VersionedFileChange, _a1 error) *MockGithubRepository_CompareFiles_Call {
|
func (_c *MockGithubRepository_CompareFiles_Call) Return(_a0 []repository.VersionedFileChange, _a1 error) *MockGithubRepository_CompareFiles_Call {
|
||||||
_c.Call.Return(_a0, _a1)
|
_c.Call.Return(_a0, _a1)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGithubRepository_CompareFiles_Call) RunAndReturn(run func(context.Context, string, string) ([]VersionedFileChange, error)) *MockGithubRepository_CompareFiles_Call {
|
func (_c *MockGithubRepository_CompareFiles_Call) RunAndReturn(run func(context.Context, string, string) ([]repository.VersionedFileChange, error)) *MockGithubRepository_CompareFiles_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
@@ -500,23 +441,23 @@ func (_c *MockGithubRepository_Owner_Call) RunAndReturn(run func() string) *Mock
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read provides a mock function with given fields: ctx, path, ref
|
// Read provides a mock function with given fields: ctx, path, ref
|
||||||
func (_m *MockGithubRepository) Read(ctx context.Context, path string, ref string) (*FileInfo, error) {
|
func (_m *MockGithubRepository) Read(ctx context.Context, path string, ref string) (*repository.FileInfo, error) {
|
||||||
ret := _m.Called(ctx, path, ref)
|
ret := _m.Called(ctx, path, ref)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for Read")
|
panic("no return value specified for Read")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 *FileInfo
|
var r0 *repository.FileInfo
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*FileInfo, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*repository.FileInfo, error)); ok {
|
||||||
return rf(ctx, path, ref)
|
return rf(ctx, path, ref)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) *FileInfo); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, string) *repository.FileInfo); ok {
|
||||||
r0 = rf(ctx, path, ref)
|
r0 = rf(ctx, path, ref)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*FileInfo)
|
r0 = ret.Get(0).(*repository.FileInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,34 +490,34 @@ func (_c *MockGithubRepository_Read_Call) Run(run func(ctx context.Context, path
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGithubRepository_Read_Call) Return(_a0 *FileInfo, _a1 error) *MockGithubRepository_Read_Call {
|
func (_c *MockGithubRepository_Read_Call) Return(_a0 *repository.FileInfo, _a1 error) *MockGithubRepository_Read_Call {
|
||||||
_c.Call.Return(_a0, _a1)
|
_c.Call.Return(_a0, _a1)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGithubRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*FileInfo, error)) *MockGithubRepository_Read_Call {
|
func (_c *MockGithubRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*repository.FileInfo, error)) *MockGithubRepository_Read_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadTree provides a mock function with given fields: ctx, ref
|
// ReadTree provides a mock function with given fields: ctx, ref
|
||||||
func (_m *MockGithubRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) {
|
func (_m *MockGithubRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
||||||
ret := _m.Called(ctx, ref)
|
ret := _m.Called(ctx, ref)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for ReadTree")
|
panic("no return value specified for ReadTree")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 []FileTreeEntry
|
var r0 []repository.FileTreeEntry
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string) ([]FileTreeEntry, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string) ([]repository.FileTreeEntry, error)); ok {
|
||||||
return rf(ctx, ref)
|
return rf(ctx, ref)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string) []FileTreeEntry); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string) []repository.FileTreeEntry); ok {
|
||||||
r0 = rf(ctx, ref)
|
r0 = rf(ctx, ref)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).([]FileTreeEntry)
|
r0 = ret.Get(0).([]repository.FileTreeEntry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,12 +549,12 @@ func (_c *MockGithubRepository_ReadTree_Call) Run(run func(ctx context.Context,
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGithubRepository_ReadTree_Call) Return(_a0 []FileTreeEntry, _a1 error) *MockGithubRepository_ReadTree_Call {
|
func (_c *MockGithubRepository_ReadTree_Call) Return(_a0 []repository.FileTreeEntry, _a1 error) *MockGithubRepository_ReadTree_Call {
|
||||||
_c.Call.Return(_a0, _a1)
|
_c.Call.Return(_a0, _a1)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGithubRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]FileTreeEntry, error)) *MockGithubRepository_ReadTree_Call {
|
func (_c *MockGithubRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]repository.FileTreeEntry, error)) *MockGithubRepository_ReadTree_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
@@ -664,7 +605,7 @@ func (_c *MockGithubRepository_Repo_Call) RunAndReturn(run func() string) *MockG
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResourceURLs provides a mock function with given fields: ctx, file
|
// ResourceURLs provides a mock function with given fields: ctx, file
|
||||||
func (_m *MockGithubRepository) ResourceURLs(ctx context.Context, file *FileInfo) (*v0alpha1.ResourceURLs, error) {
|
func (_m *MockGithubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*v0alpha1.ResourceURLs, error) {
|
||||||
ret := _m.Called(ctx, file)
|
ret := _m.Called(ctx, file)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
@@ -673,10 +614,10 @@ func (_m *MockGithubRepository) ResourceURLs(ctx context.Context, file *FileInfo
|
|||||||
|
|
||||||
var r0 *v0alpha1.ResourceURLs
|
var r0 *v0alpha1.ResourceURLs
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, *FileInfo) (*v0alpha1.ResourceURLs, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, *repository.FileInfo) (*v0alpha1.ResourceURLs, error)); ok {
|
||||||
return rf(ctx, file)
|
return rf(ctx, file)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, *FileInfo) *v0alpha1.ResourceURLs); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, *repository.FileInfo) *v0alpha1.ResourceURLs); ok {
|
||||||
r0 = rf(ctx, file)
|
r0 = rf(ctx, file)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
@@ -684,7 +625,7 @@ func (_m *MockGithubRepository) ResourceURLs(ctx context.Context, file *FileInfo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, *FileInfo) error); ok {
|
if rf, ok := ret.Get(1).(func(context.Context, *repository.FileInfo) error); ok {
|
||||||
r1 = rf(ctx, file)
|
r1 = rf(ctx, file)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
@@ -700,14 +641,14 @@ type MockGithubRepository_ResourceURLs_Call struct {
|
|||||||
|
|
||||||
// ResourceURLs is a helper method to define mock.On call
|
// ResourceURLs is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
// - file *FileInfo
|
// - file *repository.FileInfo
|
||||||
func (_e *MockGithubRepository_Expecter) ResourceURLs(ctx interface{}, file interface{}) *MockGithubRepository_ResourceURLs_Call {
|
func (_e *MockGithubRepository_Expecter) ResourceURLs(ctx interface{}, file interface{}) *MockGithubRepository_ResourceURLs_Call {
|
||||||
return &MockGithubRepository_ResourceURLs_Call{Call: _e.mock.On("ResourceURLs", ctx, file)}
|
return &MockGithubRepository_ResourceURLs_Call{Call: _e.mock.On("ResourceURLs", ctx, file)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGithubRepository_ResourceURLs_Call) Run(run func(ctx context.Context, file *FileInfo)) *MockGithubRepository_ResourceURLs_Call {
|
func (_c *MockGithubRepository_ResourceURLs_Call) Run(run func(ctx context.Context, file *repository.FileInfo)) *MockGithubRepository_ResourceURLs_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context), args[1].(*FileInfo))
|
run(args[0].(context.Context), args[1].(*repository.FileInfo))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
@@ -717,7 +658,66 @@ func (_c *MockGithubRepository_ResourceURLs_Call) Return(_a0 *v0alpha1.ResourceU
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockGithubRepository_ResourceURLs_Call) RunAndReturn(run func(context.Context, *FileInfo) (*v0alpha1.ResourceURLs, error)) *MockGithubRepository_ResourceURLs_Call {
|
func (_c *MockGithubRepository_ResourceURLs_Call) RunAndReturn(run func(context.Context, *repository.FileInfo) (*v0alpha1.ResourceURLs, error)) *MockGithubRepository_ResourceURLs_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage provides a mock function with given fields: ctx, opts
|
||||||
|
func (_m *MockGithubRepository) Stage(ctx context.Context, opts repository.StageOptions) (repository.StagedRepository, error) {
|
||||||
|
ret := _m.Called(ctx, opts)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Stage")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 repository.StagedRepository
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, repository.StageOptions) (repository.StagedRepository, error)); ok {
|
||||||
|
return rf(ctx, opts)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, repository.StageOptions) repository.StagedRepository); ok {
|
||||||
|
r0 = rf(ctx, opts)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(repository.StagedRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, repository.StageOptions) error); ok {
|
||||||
|
r1 = rf(ctx, opts)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockGithubRepository_Stage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stage'
|
||||||
|
type MockGithubRepository_Stage_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - opts repository.StageOptions
|
||||||
|
func (_e *MockGithubRepository_Expecter) Stage(ctx interface{}, opts interface{}) *MockGithubRepository_Stage_Call {
|
||||||
|
return &MockGithubRepository_Stage_Call{Call: _e.mock.On("Stage", ctx, opts)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockGithubRepository_Stage_Call) Run(run func(ctx context.Context, opts repository.StageOptions)) *MockGithubRepository_Stage_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(repository.StageOptions))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockGithubRepository_Stage_Call) Return(_a0 repository.StagedRepository, _a1 error) *MockGithubRepository_Stage_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockGithubRepository_Stage_Call) RunAndReturn(run func(context.Context, repository.StageOptions) (repository.StagedRepository, error)) *MockGithubRepository_Stage_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
@@ -8,10 +8,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-github/v70/github"
|
"github.com/google/go-github/v70/github"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type githubClient struct {
|
type githubClient struct {
|
||||||
@@ -22,268 +18,12 @@ func NewClient(client *github.Client) Client {
|
|||||||
return &githubClient{client}
|
return &githubClient{client}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *githubClient) IsAuthenticated(ctx context.Context) error {
|
|
||||||
if _, _, err := r.gh.Users.Get(ctx, ""); err != nil {
|
|
||||||
var ghErr *github.ErrorResponse
|
|
||||||
if errors.As(err, &ghErr) {
|
|
||||||
switch ghErr.Response.StatusCode {
|
|
||||||
case http.StatusUnauthorized:
|
|
||||||
return apierrors.NewUnauthorized("token is invalid or expired")
|
|
||||||
case http.StatusForbidden:
|
|
||||||
return &apierrors.StatusError{
|
|
||||||
ErrStatus: metav1.Status{
|
|
||||||
Status: metav1.StatusFailure,
|
|
||||||
Code: http.StatusUnauthorized,
|
|
||||||
Reason: metav1.StatusReasonUnauthorized,
|
|
||||||
Message: "token is revoked or has insufficient permissions",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case http.StatusServiceUnavailable:
|
|
||||||
return ErrServiceUnavailable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubClient) RepoExists(ctx context.Context, owner, repository string) (bool, error) {
|
|
||||||
_, resp, err := r.gh.Repositories.Get(ctx, owner, repository)
|
|
||||||
if err == nil {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
if resp.StatusCode == http.StatusNotFound {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxDirectoryItems = 1000 // Maximum number of items allowed in a directory
|
maxCommits = 1000 // Maximum number of commits to fetch
|
||||||
maxTreeItems = 10000 // Maximum number of items allowed in a tree
|
maxWebhooks = 100 // Maximum number of webhooks allowed per repository
|
||||||
maxCommits = 1000 // Maximum number of commits to fetch
|
maxPRFiles = 1000 // Maximum number of files allowed in a pull request
|
||||||
maxCompareFiles = 1000 // Maximum number of files to compare between commits
|
|
||||||
maxWebhooks = 100 // Maximum number of webhooks allowed per repository
|
|
||||||
maxPRFiles = 1000 // Maximum number of files allowed in a pull request
|
|
||||||
maxPullRequestsFileComments = 1000 // Maximum number of comments allowed in a pull request
|
|
||||||
maxFileSize = 10 * 1024 * 1024 // 10MB in bytes
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *githubClient) GetContents(ctx context.Context, owner, repository, path, ref string) (fileContents RepositoryContent, dirContents []RepositoryContent, err error) {
|
|
||||||
// First try to get repository contents
|
|
||||||
opts := &github.RepositoryContentGetOptions{
|
|
||||||
Ref: ref,
|
|
||||||
}
|
|
||||||
|
|
||||||
fc, dc, _, err := r.gh.Repositories.GetContents(ctx, owner, repository, path, opts)
|
|
||||||
if err != nil {
|
|
||||||
var ghErr *github.ErrorResponse
|
|
||||||
if !errors.As(err, &ghErr) {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
|
||||||
return nil, nil, ErrServiceUnavailable
|
|
||||||
}
|
|
||||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
|
||||||
return nil, nil, ErrResourceNotFound
|
|
||||||
}
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if fc != nil {
|
|
||||||
// Check file size before returning content
|
|
||||||
if fc.GetSize() > maxFileSize {
|
|
||||||
return nil, nil, ErrFileTooLarge
|
|
||||||
}
|
|
||||||
return realRepositoryContent{fc}, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// For directories, check size limits
|
|
||||||
if len(dc) > maxDirectoryItems {
|
|
||||||
return nil, nil, fmt.Errorf("directory contains too many items (more than %d)", maxDirectoryItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert directory contents
|
|
||||||
allContents := make([]RepositoryContent, 0, len(dc))
|
|
||||||
for _, original := range dc {
|
|
||||||
allContents = append(allContents, realRepositoryContent{original})
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, allContents, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubClient) GetTree(ctx context.Context, owner, repository, basePath, ref string, recursive bool) ([]RepositoryContent, bool, error) {
|
|
||||||
var tree *github.Tree
|
|
||||||
var err error
|
|
||||||
|
|
||||||
subPaths := safepath.Split(basePath)
|
|
||||||
currentRef := ref
|
|
||||||
|
|
||||||
for {
|
|
||||||
// If subPaths is empty, we can read recursively, as we're reading the tree from the "base" of the repository. Otherwise, always read only the direct children.
|
|
||||||
recursive := recursive && len(subPaths) == 0
|
|
||||||
|
|
||||||
tree, _, err = r.gh.Git.GetTree(ctx, owner, repository, currentRef, recursive)
|
|
||||||
if err != nil {
|
|
||||||
var ghErr *github.ErrorResponse
|
|
||||||
if !errors.As(err, &ghErr) {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
|
||||||
return nil, false, ErrServiceUnavailable
|
|
||||||
}
|
|
||||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
|
||||||
if currentRef != ref {
|
|
||||||
// We're operating with a subpath which doesn't exist yet.
|
|
||||||
// Pretend as if there is simply no files.
|
|
||||||
// FIXME: why should we pretend this?
|
|
||||||
return nil, false, nil
|
|
||||||
}
|
|
||||||
// currentRef == ref
|
|
||||||
// This indicates the repository or commitish reference doesn't exist. This should always return an error.
|
|
||||||
return nil, false, ErrResourceNotFound
|
|
||||||
}
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we've exceeded the maximum allowed items
|
|
||||||
if len(tree.Entries) > maxTreeItems {
|
|
||||||
return nil, false, fmt.Errorf("tree contains too many items (more than %d)", maxTreeItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prep for next iteration.
|
|
||||||
if len(subPaths) == 0 {
|
|
||||||
// We're done: we've discovered the tree we want.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// the ref must be equal the SHA of the entry corresponding to subPaths[0]
|
|
||||||
currentRef = ""
|
|
||||||
for _, e := range tree.Entries {
|
|
||||||
if e.GetPath() == subPaths[0] {
|
|
||||||
currentRef = e.GetSHA()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subPaths = subPaths[1:]
|
|
||||||
if currentRef == "" {
|
|
||||||
// We couldn't find the folder in the tree...
|
|
||||||
return nil, false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the tree is truncated and we're in recursive mode, return an error
|
|
||||||
if tree.GetTruncated() && recursive {
|
|
||||||
return nil, true, fmt.Errorf("tree is too large to fetch recursively (more than %d items)", maxTreeItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
entries := make([]RepositoryContent, 0, len(tree.Entries))
|
|
||||||
for _, te := range tree.Entries {
|
|
||||||
rrc := &realRepositoryContent{
|
|
||||||
real: &github.RepositoryContent{
|
|
||||||
Path: te.Path,
|
|
||||||
Size: te.Size,
|
|
||||||
SHA: te.SHA,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if te.GetType() == "tree" {
|
|
||||||
rrc.real.Type = github.Ptr("dir")
|
|
||||||
} else {
|
|
||||||
rrc.real.Type = te.Type
|
|
||||||
}
|
|
||||||
entries = append(entries, rrc)
|
|
||||||
}
|
|
||||||
return entries, tree.GetTruncated(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubClient) CreateFile(ctx context.Context, owner, repository, path, branch, message string, content []byte) error {
|
|
||||||
if message == "" {
|
|
||||||
message = fmt.Sprintf("Create %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err := r.gh.Repositories.CreateFile(ctx, owner, repository, path, &github.RepositoryContentFileOptions{
|
|
||||||
Branch: &branch,
|
|
||||||
Message: &message,
|
|
||||||
Content: content,
|
|
||||||
})
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var ghErr *github.ErrorResponse
|
|
||||||
if !errors.As(err, &ghErr) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ghErr.Response.StatusCode == http.StatusUnprocessableEntity {
|
|
||||||
return ErrResourceAlreadyExists
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubClient) UpdateFile(ctx context.Context, owner, repository, path, branch, message, hash string, content []byte) error {
|
|
||||||
if message == "" {
|
|
||||||
message = fmt.Sprintf("Update %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err := r.gh.Repositories.UpdateFile(ctx, owner, repository, path, &github.RepositoryContentFileOptions{
|
|
||||||
Branch: &branch,
|
|
||||||
Message: &message,
|
|
||||||
Content: content,
|
|
||||||
SHA: &hash,
|
|
||||||
})
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var ghErr *github.ErrorResponse
|
|
||||||
if !errors.As(err, &ghErr) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
|
||||||
return ErrResourceNotFound
|
|
||||||
}
|
|
||||||
if ghErr.Response.StatusCode == http.StatusConflict {
|
|
||||||
return ErrMismatchedHash
|
|
||||||
}
|
|
||||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
|
||||||
return ErrServiceUnavailable
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubClient) DeleteFile(ctx context.Context, owner, repository, path, branch, message, hash string) error {
|
|
||||||
if message == "" {
|
|
||||||
message = fmt.Sprintf("Delete %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err := r.gh.Repositories.DeleteFile(ctx, owner, repository, path, &github.RepositoryContentFileOptions{
|
|
||||||
Branch: &branch,
|
|
||||||
Message: &message,
|
|
||||||
SHA: &hash,
|
|
||||||
})
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var ghErr *github.ErrorResponse
|
|
||||||
if !errors.As(err, &ghErr) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
|
||||||
return ErrResourceNotFound
|
|
||||||
}
|
|
||||||
if ghErr.Response.StatusCode == http.StatusConflict {
|
|
||||||
return ErrMismatchedHash
|
|
||||||
}
|
|
||||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
|
||||||
return ErrServiceUnavailable
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commits returns a list of commits for a given repository and branch.
|
// Commits returns a list of commits for a given repository and branch.
|
||||||
func (r *githubClient) Commits(ctx context.Context, owner, repository, path, branch string) ([]Commit, error) {
|
func (r *githubClient) Commits(ctx context.Context, owner, repository, path, branch string) ([]Commit, error) {
|
||||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.RepositoryCommit, *github.Response, error) {
|
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.RepositoryCommit, *github.Response, error) {
|
||||||
@@ -343,105 +83,6 @@ func (r *githubClient) Commits(ctx context.Context, owner, repository, path, bra
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *githubClient) CompareCommits(ctx context.Context, owner, repository, base, head string) ([]CommitFile, error) {
|
|
||||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.CommitFile, *github.Response, error) {
|
|
||||||
compare, resp, err := r.gh.Repositories.CompareCommits(ctx, owner, repository, base, head, opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
return compare.Files, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := paginatedList(
|
|
||||||
ctx,
|
|
||||||
listFn,
|
|
||||||
defaultListOptions(maxCompareFiles),
|
|
||||||
)
|
|
||||||
if errors.Is(err, ErrTooManyItems) {
|
|
||||||
return nil, fmt.Errorf("too many files changed between commits (more than %d)", maxCompareFiles)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to the interface type
|
|
||||||
ret := make([]CommitFile, 0, len(files))
|
|
||||||
for _, f := range files {
|
|
||||||
ret = append(ret, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubClient) GetBranch(ctx context.Context, owner, repository, branchName string) (Branch, error) {
|
|
||||||
branch, resp, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0)
|
|
||||||
if err != nil {
|
|
||||||
// For some reason, GitHub client handles this case differently by failing with a wrapped error
|
|
||||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
|
||||||
return Branch{}, ErrResourceNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp != nil && resp.StatusCode == http.StatusServiceUnavailable {
|
|
||||||
return Branch{}, ErrServiceUnavailable
|
|
||||||
}
|
|
||||||
|
|
||||||
var ghErr *github.ErrorResponse
|
|
||||||
if !errors.As(err, &ghErr) {
|
|
||||||
return Branch{}, err
|
|
||||||
}
|
|
||||||
// Leaving these just in case
|
|
||||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
|
||||||
return Branch{}, ErrServiceUnavailable
|
|
||||||
}
|
|
||||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
|
||||||
return Branch{}, ErrResourceNotFound
|
|
||||||
}
|
|
||||||
return Branch{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return Branch{
|
|
||||||
Name: branch.GetName(),
|
|
||||||
Sha: branch.GetCommit().GetSHA(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubClient) CreateBranch(ctx context.Context, owner, repository, sourceBranch, branchName string) error {
|
|
||||||
// Fail if the branch already exists
|
|
||||||
if _, _, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0); err == nil {
|
|
||||||
return ErrResourceAlreadyExists
|
|
||||||
}
|
|
||||||
|
|
||||||
// Branch out based on the repository branch
|
|
||||||
baseRef, _, err := r.gh.Repositories.GetBranch(ctx, owner, repository, sourceBranch, 0)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get base branch: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, _, err := r.gh.Git.CreateRef(ctx, owner, repository, &github.Reference{
|
|
||||||
Ref: github.Ptr(fmt.Sprintf("refs/heads/%s", branchName)),
|
|
||||||
Object: &github.GitObject{
|
|
||||||
SHA: baseRef.Commit.SHA,
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("create branch ref: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubClient) BranchExists(ctx context.Context, owner, repository, branchName string) (bool, error) {
|
|
||||||
_, resp, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0)
|
|
||||||
if err == nil {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusNotFound {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubClient) ListWebhooks(ctx context.Context, owner, repository string) ([]WebhookConfig, error) {
|
func (r *githubClient) ListWebhooks(ctx context.Context, owner, repository string) ([]WebhookConfig, error) {
|
||||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
|
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
|
||||||
return r.gh.Repositories.ListHooks(ctx, owner, repository, opts)
|
return r.gh.Repositories.ListHooks(ctx, owner, repository, opts)
|
||||||
@@ -626,44 +267,6 @@ func (r *githubClient) CreatePullRequestComment(ctx context.Context, owner, repo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type realRepositoryContent struct {
|
|
||||||
real *github.RepositoryContent
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ RepositoryContent = realRepositoryContent{}
|
|
||||||
|
|
||||||
func (c realRepositoryContent) IsDirectory() bool {
|
|
||||||
return c.real.GetType() == "dir"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c realRepositoryContent) GetFileContent() (string, error) {
|
|
||||||
return c.real.GetContent()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c realRepositoryContent) IsSymlink() bool {
|
|
||||||
return c.real.Target != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c realRepositoryContent) GetPath() string {
|
|
||||||
return c.real.GetPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c realRepositoryContent) GetSHA() string {
|
|
||||||
return c.real.GetSHA()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c realRepositoryContent) GetSize() int64 {
|
|
||||||
if c.real.Size != nil {
|
|
||||||
return int64(*c.real.Size)
|
|
||||||
}
|
|
||||||
if c.real.Content != nil {
|
|
||||||
if c, err := c.real.GetContent(); err == nil {
|
|
||||||
return int64(len(c))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// listOptions represents pagination parameters for list operations
|
// listOptions represents pagination parameters for list operations
|
||||||
type listOptions struct {
|
type listOptions struct {
|
||||||
github.ListOptions
|
github.ListOptions
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
package github
|
package github
|
||||||
|
|
||||||
@@ -21,65 +21,6 @@ func (_m *MockClient) EXPECT() *MockClient_Expecter {
|
|||||||
return &MockClient_Expecter{mock: &_m.Mock}
|
return &MockClient_Expecter{mock: &_m.Mock}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BranchExists provides a mock function with given fields: ctx, owner, repository, branchName
|
|
||||||
func (_m *MockClient) BranchExists(ctx context.Context, owner string, repository string, branchName string) (bool, error) {
|
|
||||||
ret := _m.Called(ctx, owner, repository, branchName)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for BranchExists")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 bool
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (bool, error)); ok {
|
|
||||||
return rf(ctx, owner, repository, branchName)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) bool); ok {
|
|
||||||
r0 = rf(ctx, owner, repository, branchName)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
|
|
||||||
r1 = rf(ctx, owner, repository, branchName)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClient_BranchExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BranchExists'
|
|
||||||
type MockClient_BranchExists_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// BranchExists is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - owner string
|
|
||||||
// - repository string
|
|
||||||
// - branchName string
|
|
||||||
func (_e *MockClient_Expecter) BranchExists(ctx interface{}, owner interface{}, repository interface{}, branchName interface{}) *MockClient_BranchExists_Call {
|
|
||||||
return &MockClient_BranchExists_Call{Call: _e.mock.On("BranchExists", ctx, owner, repository, branchName)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_BranchExists_Call) Run(run func(ctx context.Context, owner string, repository string, branchName string)) *MockClient_BranchExists_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_BranchExists_Call) Return(_a0 bool, _a1 error) *MockClient_BranchExists_Call {
|
|
||||||
_c.Call.Return(_a0, _a1)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_BranchExists_Call) RunAndReturn(run func(context.Context, string, string, string) (bool, error)) *MockClient_BranchExists_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commits provides a mock function with given fields: ctx, owner, repository, path, branch
|
// Commits provides a mock function with given fields: ctx, owner, repository, path, branch
|
||||||
func (_m *MockClient) Commits(ctx context.Context, owner string, repository string, path string, branch string) ([]Commit, error) {
|
func (_m *MockClient) Commits(ctx context.Context, owner string, repository string, path string, branch string) ([]Commit, error) {
|
||||||
ret := _m.Called(ctx, owner, repository, path, branch)
|
ret := _m.Called(ctx, owner, repository, path, branch)
|
||||||
@@ -142,170 +83,6 @@ func (_c *MockClient_Commits_Call) RunAndReturn(run func(context.Context, string
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompareCommits provides a mock function with given fields: ctx, owner, repository, base, head
|
|
||||||
func (_m *MockClient) CompareCommits(ctx context.Context, owner string, repository string, base string, head string) ([]CommitFile, error) {
|
|
||||||
ret := _m.Called(ctx, owner, repository, base, head)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for CompareCommits")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 []CommitFile
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) ([]CommitFile, error)); ok {
|
|
||||||
return rf(ctx, owner, repository, base, head)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) []CommitFile); ok {
|
|
||||||
r0 = rf(ctx, owner, repository, base, head)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).([]CommitFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string) error); ok {
|
|
||||||
r1 = rf(ctx, owner, repository, base, head)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClient_CompareCommits_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CompareCommits'
|
|
||||||
type MockClient_CompareCommits_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompareCommits is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - owner string
|
|
||||||
// - repository string
|
|
||||||
// - base string
|
|
||||||
// - head string
|
|
||||||
func (_e *MockClient_Expecter) CompareCommits(ctx interface{}, owner interface{}, repository interface{}, base interface{}, head interface{}) *MockClient_CompareCommits_Call {
|
|
||||||
return &MockClient_CompareCommits_Call{Call: _e.mock.On("CompareCommits", ctx, owner, repository, base, head)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_CompareCommits_Call) Run(run func(ctx context.Context, owner string, repository string, base string, head string)) *MockClient_CompareCommits_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_CompareCommits_Call) Return(_a0 []CommitFile, _a1 error) *MockClient_CompareCommits_Call {
|
|
||||||
_c.Call.Return(_a0, _a1)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_CompareCommits_Call) RunAndReturn(run func(context.Context, string, string, string, string) ([]CommitFile, error)) *MockClient_CompareCommits_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateBranch provides a mock function with given fields: ctx, owner, repository, sourceBranch, branchName
|
|
||||||
func (_m *MockClient) CreateBranch(ctx context.Context, owner string, repository string, sourceBranch string, branchName string) error {
|
|
||||||
ret := _m.Called(ctx, owner, repository, sourceBranch, branchName)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for CreateBranch")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok {
|
|
||||||
r0 = rf(ctx, owner, repository, sourceBranch, branchName)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClient_CreateBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateBranch'
|
|
||||||
type MockClient_CreateBranch_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateBranch is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - owner string
|
|
||||||
// - repository string
|
|
||||||
// - sourceBranch string
|
|
||||||
// - branchName string
|
|
||||||
func (_e *MockClient_Expecter) CreateBranch(ctx interface{}, owner interface{}, repository interface{}, sourceBranch interface{}, branchName interface{}) *MockClient_CreateBranch_Call {
|
|
||||||
return &MockClient_CreateBranch_Call{Call: _e.mock.On("CreateBranch", ctx, owner, repository, sourceBranch, branchName)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_CreateBranch_Call) Run(run func(ctx context.Context, owner string, repository string, sourceBranch string, branchName string)) *MockClient_CreateBranch_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_CreateBranch_Call) Return(_a0 error) *MockClient_CreateBranch_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_CreateBranch_Call) RunAndReturn(run func(context.Context, string, string, string, string) error) *MockClient_CreateBranch_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFile provides a mock function with given fields: ctx, owner, repository, path, branch, message, content
|
|
||||||
func (_m *MockClient) CreateFile(ctx context.Context, owner string, repository string, path string, branch string, message string, content []byte) error {
|
|
||||||
ret := _m.Called(ctx, owner, repository, path, branch, message, content)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for CreateFile")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, []byte) error); ok {
|
|
||||||
r0 = rf(ctx, owner, repository, path, branch, message, content)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClient_CreateFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateFile'
|
|
||||||
type MockClient_CreateFile_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFile is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - owner string
|
|
||||||
// - repository string
|
|
||||||
// - path string
|
|
||||||
// - branch string
|
|
||||||
// - message string
|
|
||||||
// - content []byte
|
|
||||||
func (_e *MockClient_Expecter) CreateFile(ctx interface{}, owner interface{}, repository interface{}, path interface{}, branch interface{}, message interface{}, content interface{}) *MockClient_CreateFile_Call {
|
|
||||||
return &MockClient_CreateFile_Call{Call: _e.mock.On("CreateFile", ctx, owner, repository, path, branch, message, content)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_CreateFile_Call) Run(run func(ctx context.Context, owner string, repository string, path string, branch string, message string, content []byte)) *MockClient_CreateFile_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(string), args[6].([]byte))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_CreateFile_Call) Return(_a0 error) *MockClient_CreateFile_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_CreateFile_Call) RunAndReturn(run func(context.Context, string, string, string, string, string, []byte) error) *MockClient_CreateFile_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePullRequestComment provides a mock function with given fields: ctx, owner, repository, number, body
|
// CreatePullRequestComment provides a mock function with given fields: ctx, owner, repository, number, body
|
||||||
func (_m *MockClient) CreatePullRequestComment(ctx context.Context, owner string, repository string, number int, body string) error {
|
func (_m *MockClient) CreatePullRequestComment(ctx context.Context, owner string, repository string, number int, body string) error {
|
||||||
ret := _m.Called(ctx, owner, repository, number, body)
|
ret := _m.Called(ctx, owner, repository, number, body)
|
||||||
@@ -415,58 +192,6 @@ func (_c *MockClient_CreateWebhook_Call) RunAndReturn(run func(context.Context,
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteFile provides a mock function with given fields: ctx, owner, repository, path, branch, message, hash
|
|
||||||
func (_m *MockClient) DeleteFile(ctx context.Context, owner string, repository string, path string, branch string, message string, hash string) error {
|
|
||||||
ret := _m.Called(ctx, owner, repository, path, branch, message, hash)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for DeleteFile")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, string) error); ok {
|
|
||||||
r0 = rf(ctx, owner, repository, path, branch, message, hash)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClient_DeleteFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteFile'
|
|
||||||
type MockClient_DeleteFile_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteFile is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - owner string
|
|
||||||
// - repository string
|
|
||||||
// - path string
|
|
||||||
// - branch string
|
|
||||||
// - message string
|
|
||||||
// - hash string
|
|
||||||
func (_e *MockClient_Expecter) DeleteFile(ctx interface{}, owner interface{}, repository interface{}, path interface{}, branch interface{}, message interface{}, hash interface{}) *MockClient_DeleteFile_Call {
|
|
||||||
return &MockClient_DeleteFile_Call{Call: _e.mock.On("DeleteFile", ctx, owner, repository, path, branch, message, hash)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_DeleteFile_Call) Run(run func(ctx context.Context, owner string, repository string, path string, branch string, message string, hash string)) *MockClient_DeleteFile_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(string), args[6].(string))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_DeleteFile_Call) Return(_a0 error) *MockClient_DeleteFile_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_DeleteFile_Call) RunAndReturn(run func(context.Context, string, string, string, string, string, string) error) *MockClient_DeleteFile_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteWebhook provides a mock function with given fields: ctx, owner, repository, webhookID
|
// DeleteWebhook provides a mock function with given fields: ctx, owner, repository, webhookID
|
||||||
func (_m *MockClient) DeleteWebhook(ctx context.Context, owner string, repository string, webhookID int64) error {
|
func (_m *MockClient) DeleteWebhook(ctx context.Context, owner string, repository string, webhookID int64) error {
|
||||||
ret := _m.Called(ctx, owner, repository, webhookID)
|
ret := _m.Called(ctx, owner, repository, webhookID)
|
||||||
@@ -565,206 +290,6 @@ func (_c *MockClient_EditWebhook_Call) RunAndReturn(run func(context.Context, st
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBranch provides a mock function with given fields: ctx, owner, repository, branchName
|
|
||||||
func (_m *MockClient) GetBranch(ctx context.Context, owner string, repository string, branchName string) (Branch, error) {
|
|
||||||
ret := _m.Called(ctx, owner, repository, branchName)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for GetBranch")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 Branch
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (Branch, error)); ok {
|
|
||||||
return rf(ctx, owner, repository, branchName)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) Branch); ok {
|
|
||||||
r0 = rf(ctx, owner, repository, branchName)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(Branch)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
|
|
||||||
r1 = rf(ctx, owner, repository, branchName)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClient_GetBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBranch'
|
|
||||||
type MockClient_GetBranch_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBranch is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - owner string
|
|
||||||
// - repository string
|
|
||||||
// - branchName string
|
|
||||||
func (_e *MockClient_Expecter) GetBranch(ctx interface{}, owner interface{}, repository interface{}, branchName interface{}) *MockClient_GetBranch_Call {
|
|
||||||
return &MockClient_GetBranch_Call{Call: _e.mock.On("GetBranch", ctx, owner, repository, branchName)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_GetBranch_Call) Run(run func(ctx context.Context, owner string, repository string, branchName string)) *MockClient_GetBranch_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_GetBranch_Call) Return(_a0 Branch, _a1 error) *MockClient_GetBranch_Call {
|
|
||||||
_c.Call.Return(_a0, _a1)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_GetBranch_Call) RunAndReturn(run func(context.Context, string, string, string) (Branch, error)) *MockClient_GetBranch_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContents provides a mock function with given fields: ctx, owner, repository, path, ref
|
|
||||||
func (_m *MockClient) GetContents(ctx context.Context, owner string, repository string, path string, ref string) (RepositoryContent, []RepositoryContent, error) {
|
|
||||||
ret := _m.Called(ctx, owner, repository, path, ref)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for GetContents")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 RepositoryContent
|
|
||||||
var r1 []RepositoryContent
|
|
||||||
var r2 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) (RepositoryContent, []RepositoryContent, error)); ok {
|
|
||||||
return rf(ctx, owner, repository, path, ref)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) RepositoryContent); ok {
|
|
||||||
r0 = rf(ctx, owner, repository, path, ref)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).(RepositoryContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string) []RepositoryContent); ok {
|
|
||||||
r1 = rf(ctx, owner, repository, path, ref)
|
|
||||||
} else {
|
|
||||||
if ret.Get(1) != nil {
|
|
||||||
r1 = ret.Get(1).([]RepositoryContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(2).(func(context.Context, string, string, string, string) error); ok {
|
|
||||||
r2 = rf(ctx, owner, repository, path, ref)
|
|
||||||
} else {
|
|
||||||
r2 = ret.Error(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1, r2
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClient_GetContents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetContents'
|
|
||||||
type MockClient_GetContents_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContents is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - owner string
|
|
||||||
// - repository string
|
|
||||||
// - path string
|
|
||||||
// - ref string
|
|
||||||
func (_e *MockClient_Expecter) GetContents(ctx interface{}, owner interface{}, repository interface{}, path interface{}, ref interface{}) *MockClient_GetContents_Call {
|
|
||||||
return &MockClient_GetContents_Call{Call: _e.mock.On("GetContents", ctx, owner, repository, path, ref)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_GetContents_Call) Run(run func(ctx context.Context, owner string, repository string, path string, ref string)) *MockClient_GetContents_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_GetContents_Call) Return(fileContents RepositoryContent, dirContents []RepositoryContent, err error) *MockClient_GetContents_Call {
|
|
||||||
_c.Call.Return(fileContents, dirContents, err)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_GetContents_Call) RunAndReturn(run func(context.Context, string, string, string, string) (RepositoryContent, []RepositoryContent, error)) *MockClient_GetContents_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTree provides a mock function with given fields: ctx, owner, repository, basePath, ref, recursive
|
|
||||||
func (_m *MockClient) GetTree(ctx context.Context, owner string, repository string, basePath string, ref string, recursive bool) ([]RepositoryContent, bool, error) {
|
|
||||||
ret := _m.Called(ctx, owner, repository, basePath, ref, recursive)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for GetTree")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 []RepositoryContent
|
|
||||||
var r1 bool
|
|
||||||
var r2 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool) ([]RepositoryContent, bool, error)); ok {
|
|
||||||
return rf(ctx, owner, repository, basePath, ref, recursive)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool) []RepositoryContent); ok {
|
|
||||||
r0 = rf(ctx, owner, repository, basePath, ref, recursive)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).([]RepositoryContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string, bool) bool); ok {
|
|
||||||
r1 = rf(ctx, owner, repository, basePath, ref, recursive)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Get(1).(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(2).(func(context.Context, string, string, string, string, bool) error); ok {
|
|
||||||
r2 = rf(ctx, owner, repository, basePath, ref, recursive)
|
|
||||||
} else {
|
|
||||||
r2 = ret.Error(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1, r2
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClient_GetTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTree'
|
|
||||||
type MockClient_GetTree_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTree is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - owner string
|
|
||||||
// - repository string
|
|
||||||
// - basePath string
|
|
||||||
// - ref string
|
|
||||||
// - recursive bool
|
|
||||||
func (_e *MockClient_Expecter) GetTree(ctx interface{}, owner interface{}, repository interface{}, basePath interface{}, ref interface{}, recursive interface{}) *MockClient_GetTree_Call {
|
|
||||||
return &MockClient_GetTree_Call{Call: _e.mock.On("GetTree", ctx, owner, repository, basePath, ref, recursive)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_GetTree_Call) Run(run func(ctx context.Context, owner string, repository string, basePath string, ref string, recursive bool)) *MockClient_GetTree_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(bool))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_GetTree_Call) Return(entries []RepositoryContent, truncated bool, err error) *MockClient_GetTree_Call {
|
|
||||||
_c.Call.Return(entries, truncated, err)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_GetTree_Call) RunAndReturn(run func(context.Context, string, string, string, string, bool) ([]RepositoryContent, bool, error)) *MockClient_GetTree_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWebhook provides a mock function with given fields: ctx, owner, repository, webhookID
|
// GetWebhook provides a mock function with given fields: ctx, owner, repository, webhookID
|
||||||
func (_m *MockClient) GetWebhook(ctx context.Context, owner string, repository string, webhookID int64) (WebhookConfig, error) {
|
func (_m *MockClient) GetWebhook(ctx context.Context, owner string, repository string, webhookID int64) (WebhookConfig, error) {
|
||||||
ret := _m.Called(ctx, owner, repository, webhookID)
|
ret := _m.Called(ctx, owner, repository, webhookID)
|
||||||
@@ -824,52 +349,6 @@ func (_c *MockClient_GetWebhook_Call) RunAndReturn(run func(context.Context, str
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAuthenticated provides a mock function with given fields: ctx
|
|
||||||
func (_m *MockClient) IsAuthenticated(ctx context.Context) error {
|
|
||||||
ret := _m.Called(ctx)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for IsAuthenticated")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
|
||||||
r0 = rf(ctx)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClient_IsAuthenticated_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsAuthenticated'
|
|
||||||
type MockClient_IsAuthenticated_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAuthenticated is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
func (_e *MockClient_Expecter) IsAuthenticated(ctx interface{}) *MockClient_IsAuthenticated_Call {
|
|
||||||
return &MockClient_IsAuthenticated_Call{Call: _e.mock.On("IsAuthenticated", ctx)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_IsAuthenticated_Call) Run(run func(ctx context.Context)) *MockClient_IsAuthenticated_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_IsAuthenticated_Call) Return(_a0 error) *MockClient_IsAuthenticated_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_IsAuthenticated_Call) RunAndReturn(run func(context.Context) error) *MockClient_IsAuthenticated_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPullRequestFiles provides a mock function with given fields: ctx, owner, repository, number
|
// ListPullRequestFiles provides a mock function with given fields: ctx, owner, repository, number
|
||||||
func (_m *MockClient) ListPullRequestFiles(ctx context.Context, owner string, repository string, number int) ([]CommitFile, error) {
|
func (_m *MockClient) ListPullRequestFiles(ctx context.Context, owner string, repository string, number int) ([]CommitFile, error) {
|
||||||
ret := _m.Called(ctx, owner, repository, number)
|
ret := _m.Called(ctx, owner, repository, number)
|
||||||
@@ -991,117 +470,6 @@ func (_c *MockClient_ListWebhooks_Call) RunAndReturn(run func(context.Context, s
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepoExists provides a mock function with given fields: ctx, owner, repository
|
|
||||||
func (_m *MockClient) RepoExists(ctx context.Context, owner string, repository string) (bool, error) {
|
|
||||||
ret := _m.Called(ctx, owner, repository)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for RepoExists")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 bool
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok {
|
|
||||||
return rf(ctx, owner, repository)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) bool); ok {
|
|
||||||
r0 = rf(ctx, owner, repository)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
|
||||||
r1 = rf(ctx, owner, repository)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClient_RepoExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepoExists'
|
|
||||||
type MockClient_RepoExists_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// RepoExists is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - owner string
|
|
||||||
// - repository string
|
|
||||||
func (_e *MockClient_Expecter) RepoExists(ctx interface{}, owner interface{}, repository interface{}) *MockClient_RepoExists_Call {
|
|
||||||
return &MockClient_RepoExists_Call{Call: _e.mock.On("RepoExists", ctx, owner, repository)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_RepoExists_Call) Run(run func(ctx context.Context, owner string, repository string)) *MockClient_RepoExists_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_RepoExists_Call) Return(_a0 bool, _a1 error) *MockClient_RepoExists_Call {
|
|
||||||
_c.Call.Return(_a0, _a1)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_RepoExists_Call) RunAndReturn(run func(context.Context, string, string) (bool, error)) *MockClient_RepoExists_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateFile provides a mock function with given fields: ctx, owner, repository, path, branch, message, hash, content
|
|
||||||
func (_m *MockClient) UpdateFile(ctx context.Context, owner string, repository string, path string, branch string, message string, hash string, content []byte) error {
|
|
||||||
ret := _m.Called(ctx, owner, repository, path, branch, message, hash, content)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for UpdateFile")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, string, []byte) error); ok {
|
|
||||||
r0 = rf(ctx, owner, repository, path, branch, message, hash, content)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClient_UpdateFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateFile'
|
|
||||||
type MockClient_UpdateFile_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateFile is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - owner string
|
|
||||||
// - repository string
|
|
||||||
// - path string
|
|
||||||
// - branch string
|
|
||||||
// - message string
|
|
||||||
// - hash string
|
|
||||||
// - content []byte
|
|
||||||
func (_e *MockClient_Expecter) UpdateFile(ctx interface{}, owner interface{}, repository interface{}, path interface{}, branch interface{}, message interface{}, hash interface{}, content interface{}) *MockClient_UpdateFile_Call {
|
|
||||||
return &MockClient_UpdateFile_Call{Call: _e.mock.On("UpdateFile", ctx, owner, repository, path, branch, message, hash, content)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_UpdateFile_Call) Run(run func(ctx context.Context, owner string, repository string, path string, branch string, message string, hash string, content []byte)) *MockClient_UpdateFile_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(string), args[6].(string), args[7].([]byte))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_UpdateFile_Call) Return(_a0 error) *MockClient_UpdateFile_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockClient_UpdateFile_Call) RunAndReturn(run func(context.Context, string, string, string, string, string, string, []byte) error) *MockClient_UpdateFile_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
// The first argument is typically a *testing.T value.
|
// The first argument is typically a *testing.T value.
|
||||||
func NewMockClient(t interface {
|
func NewMockClient(t interface {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
package github
|
package github
|
||||||
|
|
||||||
|
|||||||
@@ -1,312 +0,0 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
|
||||||
|
|
||||||
package github
|
|
||||||
|
|
||||||
import mock "github.com/stretchr/testify/mock"
|
|
||||||
|
|
||||||
// MockRepositoryContent is an autogenerated mock type for the RepositoryContent type
|
|
||||||
type MockRepositoryContent struct {
|
|
||||||
mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockRepositoryContent_Expecter struct {
|
|
||||||
mock *mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_m *MockRepositoryContent) EXPECT() *MockRepositoryContent_Expecter {
|
|
||||||
return &MockRepositoryContent_Expecter{mock: &_m.Mock}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFileContent provides a mock function with no fields
|
|
||||||
func (_m *MockRepositoryContent) GetFileContent() (string, error) {
|
|
||||||
ret := _m.Called()
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for GetFileContent")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 string
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func() (string, error)); ok {
|
|
||||||
return rf()
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func() string); ok {
|
|
||||||
r0 = rf()
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func() error); ok {
|
|
||||||
r1 = rf()
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRepositoryContent_GetFileContent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFileContent'
|
|
||||||
type MockRepositoryContent_GetFileContent_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFileContent is a helper method to define mock.On call
|
|
||||||
func (_e *MockRepositoryContent_Expecter) GetFileContent() *MockRepositoryContent_GetFileContent_Call {
|
|
||||||
return &MockRepositoryContent_GetFileContent_Call{Call: _e.mock.On("GetFileContent")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_GetFileContent_Call) Run(run func()) *MockRepositoryContent_GetFileContent_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run()
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_GetFileContent_Call) Return(_a0 string, _a1 error) *MockRepositoryContent_GetFileContent_Call {
|
|
||||||
_c.Call.Return(_a0, _a1)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_GetFileContent_Call) RunAndReturn(run func() (string, error)) *MockRepositoryContent_GetFileContent_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPath provides a mock function with no fields
|
|
||||||
func (_m *MockRepositoryContent) GetPath() string {
|
|
||||||
ret := _m.Called()
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for GetPath")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 string
|
|
||||||
if rf, ok := ret.Get(0).(func() string); ok {
|
|
||||||
r0 = rf()
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRepositoryContent_GetPath_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPath'
|
|
||||||
type MockRepositoryContent_GetPath_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPath is a helper method to define mock.On call
|
|
||||||
func (_e *MockRepositoryContent_Expecter) GetPath() *MockRepositoryContent_GetPath_Call {
|
|
||||||
return &MockRepositoryContent_GetPath_Call{Call: _e.mock.On("GetPath")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_GetPath_Call) Run(run func()) *MockRepositoryContent_GetPath_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run()
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_GetPath_Call) Return(_a0 string) *MockRepositoryContent_GetPath_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_GetPath_Call) RunAndReturn(run func() string) *MockRepositoryContent_GetPath_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSHA provides a mock function with no fields
|
|
||||||
func (_m *MockRepositoryContent) GetSHA() string {
|
|
||||||
ret := _m.Called()
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for GetSHA")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 string
|
|
||||||
if rf, ok := ret.Get(0).(func() string); ok {
|
|
||||||
r0 = rf()
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRepositoryContent_GetSHA_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSHA'
|
|
||||||
type MockRepositoryContent_GetSHA_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSHA is a helper method to define mock.On call
|
|
||||||
func (_e *MockRepositoryContent_Expecter) GetSHA() *MockRepositoryContent_GetSHA_Call {
|
|
||||||
return &MockRepositoryContent_GetSHA_Call{Call: _e.mock.On("GetSHA")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_GetSHA_Call) Run(run func()) *MockRepositoryContent_GetSHA_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run()
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_GetSHA_Call) Return(_a0 string) *MockRepositoryContent_GetSHA_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_GetSHA_Call) RunAndReturn(run func() string) *MockRepositoryContent_GetSHA_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSize provides a mock function with no fields
|
|
||||||
func (_m *MockRepositoryContent) GetSize() int64 {
|
|
||||||
ret := _m.Called()
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for GetSize")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 int64
|
|
||||||
if rf, ok := ret.Get(0).(func() int64); ok {
|
|
||||||
r0 = rf()
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(int64)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRepositoryContent_GetSize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSize'
|
|
||||||
type MockRepositoryContent_GetSize_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSize is a helper method to define mock.On call
|
|
||||||
func (_e *MockRepositoryContent_Expecter) GetSize() *MockRepositoryContent_GetSize_Call {
|
|
||||||
return &MockRepositoryContent_GetSize_Call{Call: _e.mock.On("GetSize")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_GetSize_Call) Run(run func()) *MockRepositoryContent_GetSize_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run()
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_GetSize_Call) Return(_a0 int64) *MockRepositoryContent_GetSize_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_GetSize_Call) RunAndReturn(run func() int64) *MockRepositoryContent_GetSize_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDirectory provides a mock function with no fields
|
|
||||||
func (_m *MockRepositoryContent) IsDirectory() bool {
|
|
||||||
ret := _m.Called()
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for IsDirectory")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 bool
|
|
||||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
|
||||||
r0 = rf()
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRepositoryContent_IsDirectory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsDirectory'
|
|
||||||
type MockRepositoryContent_IsDirectory_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDirectory is a helper method to define mock.On call
|
|
||||||
func (_e *MockRepositoryContent_Expecter) IsDirectory() *MockRepositoryContent_IsDirectory_Call {
|
|
||||||
return &MockRepositoryContent_IsDirectory_Call{Call: _e.mock.On("IsDirectory")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_IsDirectory_Call) Run(run func()) *MockRepositoryContent_IsDirectory_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run()
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_IsDirectory_Call) Return(_a0 bool) *MockRepositoryContent_IsDirectory_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_IsDirectory_Call) RunAndReturn(run func() bool) *MockRepositoryContent_IsDirectory_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSymlink provides a mock function with no fields
|
|
||||||
func (_m *MockRepositoryContent) IsSymlink() bool {
|
|
||||||
ret := _m.Called()
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for IsSymlink")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 bool
|
|
||||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
|
||||||
r0 = rf()
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRepositoryContent_IsSymlink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsSymlink'
|
|
||||||
type MockRepositoryContent_IsSymlink_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSymlink is a helper method to define mock.On call
|
|
||||||
func (_e *MockRepositoryContent_Expecter) IsSymlink() *MockRepositoryContent_IsSymlink_Call {
|
|
||||||
return &MockRepositoryContent_IsSymlink_Call{Call: _e.mock.On("IsSymlink")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_IsSymlink_Call) Run(run func()) *MockRepositoryContent_IsSymlink_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run()
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_IsSymlink_Call) Return(_a0 bool) *MockRepositoryContent_IsSymlink_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepositoryContent_IsSymlink_Call) RunAndReturn(run func() bool) *MockRepositoryContent_IsSymlink_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockRepositoryContent creates a new instance of MockRepositoryContent. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
|
||||||
// The first argument is typically a *testing.T value.
|
|
||||||
func NewMockRepositoryContent(t interface {
|
|
||||||
mock.TestingT
|
|
||||||
Cleanup(func())
|
|
||||||
}) *MockRepositoryContent {
|
|
||||||
mock := &MockRepositoryContent{}
|
|
||||||
mock.Mock.Test(t)
|
|
||||||
|
|
||||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
|
||||||
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
|
||||||
|
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/git"
|
||||||
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Make sure all public functions of this struct call the (*githubRepository).logger function, to ensure the GH repo details are included.
|
||||||
|
type githubRepository struct {
|
||||||
|
gitRepo git.GitRepository
|
||||||
|
config *provisioning.Repository
|
||||||
|
gh Client // assumes github.com base URL
|
||||||
|
|
||||||
|
owner string
|
||||||
|
repo string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GithubRepository is an interface that combines all repository capabilities
|
||||||
|
// needed for GitHub repositories.
|
||||||
|
|
||||||
|
//go:generate mockery --name GithubRepository --structname MockGithubRepository --inpackage --filename github_repository_mock.go --with-expecter
|
||||||
|
type GithubRepository interface {
|
||||||
|
repository.Repository
|
||||||
|
repository.Versioned
|
||||||
|
repository.Writer
|
||||||
|
repository.Reader
|
||||||
|
repository.RepositoryWithURLs
|
||||||
|
repository.StageableRepository
|
||||||
|
Owner() string
|
||||||
|
Repo() string
|
||||||
|
Client() Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGitHub(
|
||||||
|
ctx context.Context,
|
||||||
|
config *provisioning.Repository,
|
||||||
|
gitRepo git.GitRepository,
|
||||||
|
factory *Factory,
|
||||||
|
token string,
|
||||||
|
) (GithubRepository, error) {
|
||||||
|
owner, repo, err := ParseOwnerRepoGithub(config.Spec.GitHub.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse owner and repo: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &githubRepository{
|
||||||
|
config: config,
|
||||||
|
gitRepo: gitRepo,
|
||||||
|
gh: factory.New(ctx, token), // TODO, baseURL from config
|
||||||
|
owner: owner,
|
||||||
|
repo: repo,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) Config() *provisioning.Repository {
|
||||||
|
return r.gitRepo.Config()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) Owner() string {
|
||||||
|
return r.owner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) Repo() string {
|
||||||
|
return r.repo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) Client() Client {
|
||||||
|
return r.gh
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate implements provisioning.Repository.
|
||||||
|
func (r *githubRepository) Validate() (list field.ErrorList) {
|
||||||
|
cfg := r.gitRepo.Config()
|
||||||
|
gh := cfg.Spec.GitHub
|
||||||
|
if gh == nil {
|
||||||
|
list = append(list, field.Required(field.NewPath("spec", "github"), "a github config is required"))
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
if gh.URL == "" {
|
||||||
|
list = append(list, field.Required(field.NewPath("spec", "github", "url"), "a github url is required"))
|
||||||
|
} else {
|
||||||
|
_, _, err := ParseOwnerRepoGithub(gh.URL)
|
||||||
|
if err != nil {
|
||||||
|
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, err.Error()))
|
||||||
|
} else if !strings.HasPrefix(gh.URL, "https://github.com/") {
|
||||||
|
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, "URL must start with https://github.com/"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(list) > 0 {
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.gitRepo.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseOwnerRepoGithub(giturl string) (owner string, repo string, err error) {
|
||||||
|
parsed, e := url.Parse(strings.TrimSuffix(giturl, ".git"))
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
parts := strings.Split(parsed.Path, "/")
|
||||||
|
if len(parts) < 3 {
|
||||||
|
err = fmt.Errorf("unable to parse repo+owner from url")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return parts[1], parts[2], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test implements provisioning.Repository.
|
||||||
|
func (r *githubRepository) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
||||||
|
url := r.config.Spec.GitHub.URL
|
||||||
|
_, _, err := ParseOwnerRepoGithub(url)
|
||||||
|
if err != nil {
|
||||||
|
return repository.FromFieldError(field.Invalid(
|
||||||
|
field.NewPath("spec", "github", "url"), url, err.Error())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.gitRepo.Test(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadResource implements provisioning.Repository.
|
||||||
|
func (r *githubRepository) Read(ctx context.Context, filePath, ref string) (*repository.FileInfo, error) {
|
||||||
|
return r.gitRepo.Read(ctx, filePath, ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
||||||
|
return r.gitRepo.ReadTree(ctx, ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) Create(ctx context.Context, path, ref string, data []byte, comment string) error {
|
||||||
|
return r.gitRepo.Create(ctx, path, ref, data, comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) Update(ctx context.Context, path, ref string, data []byte, comment string) error {
|
||||||
|
return r.gitRepo.Update(ctx, path, ref, data, comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||||
|
return r.gitRepo.Write(ctx, path, ref, data, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) Delete(ctx context.Context, path, ref, comment string) error {
|
||||||
|
return r.gitRepo.Delete(ctx, path, ref, comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) History(ctx context.Context, path, ref string) ([]provisioning.HistoryItem, error) {
|
||||||
|
if ref == "" {
|
||||||
|
ref = r.config.Spec.GitHub.Branch
|
||||||
|
}
|
||||||
|
|
||||||
|
finalPath := safepath.Join(r.config.Spec.GitHub.Path, path)
|
||||||
|
commits, err := r.gh.Commits(ctx, r.owner, r.repo, finalPath, ref)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrResourceNotFound) {
|
||||||
|
return nil, repository.ErrFileNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("get commits: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make([]provisioning.HistoryItem, 0, len(commits))
|
||||||
|
for _, commit := range commits {
|
||||||
|
authors := make([]provisioning.Author, 0)
|
||||||
|
if commit.Author != nil {
|
||||||
|
authors = append(authors, provisioning.Author{
|
||||||
|
Name: commit.Author.Name,
|
||||||
|
Username: commit.Author.Username,
|
||||||
|
AvatarURL: commit.Author.AvatarURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if commit.Committer != nil && commit.Author != nil && commit.Author.Name != commit.Committer.Name {
|
||||||
|
authors = append(authors, provisioning.Author{
|
||||||
|
Name: commit.Committer.Name,
|
||||||
|
Username: commit.Committer.Username,
|
||||||
|
AvatarURL: commit.Committer.AvatarURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, provisioning.HistoryItem{
|
||||||
|
Ref: commit.Ref,
|
||||||
|
Message: commit.Message,
|
||||||
|
Authors: authors,
|
||||||
|
CreatedAt: commit.CreatedAt.UnixMilli(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) LatestRef(ctx context.Context) (string, error) {
|
||||||
|
return r.gitRepo.LatestRef(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) CompareFiles(ctx context.Context, base, ref string) ([]repository.VersionedFileChange, error) {
|
||||||
|
return r.gitRepo.CompareFiles(ctx, base, ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceURLs implements RepositoryWithURLs.
|
||||||
|
func (r *githubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*provisioning.ResourceURLs, error) {
|
||||||
|
cfg := r.config.Spec.GitHub
|
||||||
|
if file.Path == "" || cfg == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ref := file.Ref
|
||||||
|
if ref == "" {
|
||||||
|
ref = cfg.Branch
|
||||||
|
}
|
||||||
|
|
||||||
|
urls := &provisioning.ResourceURLs{
|
||||||
|
RepositoryURL: cfg.URL,
|
||||||
|
SourceURL: fmt.Sprintf("%s/blob/%s/%s", cfg.URL, ref, file.Path),
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref != cfg.Branch {
|
||||||
|
urls.CompareURL = fmt.Sprintf("%s/compare/%s...%s", cfg.URL, cfg.Branch, ref)
|
||||||
|
|
||||||
|
// Create a new pull request
|
||||||
|
urls.NewPullRequestURL = fmt.Sprintf("%s?quick_pull=1&labels=grafana", urls.CompareURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *githubRepository) Stage(ctx context.Context, opts repository.StageOptions) (repository.StagedRepository, error) {
|
||||||
|
return r.gitRepo.Stage(ctx, opts)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,47 +0,0 @@
|
|||||||
package gogit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Progress(lines func(line string), final string) io.WriteCloser {
|
|
||||||
reader, writer := io.Pipe()
|
|
||||||
scanner := bufio.NewScanner(reader)
|
|
||||||
scanner.Split(scanLines)
|
|
||||||
go func() {
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
if line != "" {
|
|
||||||
lines(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lines(final)
|
|
||||||
}()
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from bufio.ScanLines and modifed to accept standalone \r as input
|
|
||||||
func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
|
||||||
if atEOF && len(data) == 0 {
|
|
||||||
return 0, nil, nil
|
|
||||||
}
|
|
||||||
if i := bytes.IndexByte(data, '\r'); i >= 0 {
|
|
||||||
// We have a full newline-terminated line.
|
|
||||||
return i + 1, data[0:i], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support standalone newlines also
|
|
||||||
if i := bytes.IndexByte(data, '\n'); i >= 0 {
|
|
||||||
// We have a full newline-terminated line.
|
|
||||||
return i + 1, data[0:i], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're at EOF, we have a final, non-terminated line. Return it.
|
|
||||||
if atEOF {
|
|
||||||
return len(data), data, nil
|
|
||||||
}
|
|
||||||
// Request more data.
|
|
||||||
return 0, nil, nil
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package gogit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestProgressParsing(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
expect []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no breaks",
|
|
||||||
input: "some text",
|
|
||||||
expect: []string{"some text"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with cr",
|
|
||||||
input: "hello\rworld",
|
|
||||||
expect: []string{"hello", "world"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with nl",
|
|
||||||
input: "hello\nworld",
|
|
||||||
expect: []string{"hello", "world"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with cr+nl",
|
|
||||||
input: "hello\r\nworld",
|
|
||||||
expect: []string{"hello", "world"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
lastLine := "***LAST*LINE***"
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
lines := []string{}
|
|
||||||
writer := Progress(func(line string) {
|
|
||||||
lines = append(lines, line)
|
|
||||||
}, lastLine)
|
|
||||||
_, _ = writer.Write([]byte(tt.input))
|
|
||||||
err := writer.Close()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
|
||||||
assert.NotEmpty(c, lines)
|
|
||||||
assert.Equal(c, lastLine, lines[len(lines)-1])
|
|
||||||
|
|
||||||
// Compare the results
|
|
||||||
require.Equal(c, tt.expect, lines[0:len(lines)-1])
|
|
||||||
}, time.Millisecond*100, time.Microsecond*50)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
|
||||||
|
|
||||||
package gogit
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
|
|
||||||
git "github.com/go-git/go-git/v5"
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockRepository is an autogenerated mock type for the Repository type
|
|
||||||
type MockRepository struct {
|
|
||||||
mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockRepository_Expecter struct {
|
|
||||||
mock *mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_m *MockRepository) EXPECT() *MockRepository_Expecter {
|
|
||||||
return &MockRepository_Expecter{mock: &_m.Mock}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PushContext provides a mock function with given fields: ctx, o
|
|
||||||
func (_m *MockRepository) PushContext(ctx context.Context, o *git.PushOptions) error {
|
|
||||||
ret := _m.Called(ctx, o)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for PushContext")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, *git.PushOptions) error); ok {
|
|
||||||
r0 = rf(ctx, o)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRepository_PushContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PushContext'
|
|
||||||
type MockRepository_PushContext_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// PushContext is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - o *git.PushOptions
|
|
||||||
func (_e *MockRepository_Expecter) PushContext(ctx interface{}, o interface{}) *MockRepository_PushContext_Call {
|
|
||||||
return &MockRepository_PushContext_Call{Call: _e.mock.On("PushContext", ctx, o)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepository_PushContext_Call) Run(run func(ctx context.Context, o *git.PushOptions)) *MockRepository_PushContext_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(*git.PushOptions))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepository_PushContext_Call) Return(_a0 error) *MockRepository_PushContext_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockRepository_PushContext_Call) RunAndReturn(run func(context.Context, *git.PushOptions) error) *MockRepository_PushContext_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockRepository creates a new instance of MockRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
|
||||||
// The first argument is typically a *testing.T value.
|
|
||||||
func NewMockRepository(t interface {
|
|
||||||
mock.TestingT
|
|
||||||
Cleanup(func())
|
|
||||||
}) *MockRepository {
|
|
||||||
mock := &MockRepository{}
|
|
||||||
mock.Mock.Test(t)
|
|
||||||
|
|
||||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
|
||||||
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package gogit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/util/httpclient"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errBytesLimitExceeded = fmt.Errorf("bytes limit exceeded")
|
|
||||||
|
|
||||||
// ByteLimitedTransport wraps http.RoundTripper to enforce a max byte limit
|
|
||||||
type ByteLimitedTransport struct {
|
|
||||||
Transport http.RoundTripper
|
|
||||||
Limit int64
|
|
||||||
Bytes int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewByteLimitedTransport creates a new ByteLimitedTransport with the specified transport and byte limit.
|
|
||||||
// If transport is nil, a new http.Transport modeled after http.DefaultTransport will be used.
|
|
||||||
func NewByteLimitedTransport(transport http.RoundTripper, limit int64) *ByteLimitedTransport {
|
|
||||||
if transport == nil {
|
|
||||||
transport = httpclient.NewHTTPTransport()
|
|
||||||
}
|
|
||||||
return &ByteLimitedTransport{
|
|
||||||
Transport: transport,
|
|
||||||
Limit: limit,
|
|
||||||
Bytes: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundTrip tracks downloaded bytes and aborts if limit is exceeded
|
|
||||||
func (b *ByteLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
resp, err := b.Transport.RoundTrip(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap response body to track bytes read
|
|
||||||
resp.Body = &byteLimitedReader{
|
|
||||||
reader: resp.Body,
|
|
||||||
limit: b.Limit,
|
|
||||||
bytes: &b.Bytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// byteLimitedReader tracks and enforces a download limit
|
|
||||||
type byteLimitedReader struct {
|
|
||||||
reader io.ReadCloser
|
|
||||||
limit int64
|
|
||||||
bytes *int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *byteLimitedReader) Read(p []byte) (int, error) {
|
|
||||||
n, err := r.reader.Read(p)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if atomic.AddInt64(r.bytes, int64(n)) > r.limit {
|
|
||||||
return 0, errBytesLimitExceeded
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *byteLimitedReader) Close() error {
|
|
||||||
return r.reader.Close()
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
package gogit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mockTransport struct {
|
|
||||||
response *http.Response
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockTransport) RoundTrip(*http.Request) (*http.Response, error) {
|
|
||||||
return m.response, m.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewByteLimitedTransport(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
transport http.RoundTripper
|
|
||||||
limit int64
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "with custom transport",
|
|
||||||
transport: &mockTransport{},
|
|
||||||
limit: 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with nil transport",
|
|
||||||
transport: nil,
|
|
||||||
limit: 1000,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
blt := NewByteLimitedTransport(tt.transport, tt.limit)
|
|
||||||
assert.NotNil(t, blt)
|
|
||||||
assert.Equal(t, tt.limit, blt.Limit)
|
|
||||||
assert.Equal(t, int64(0), blt.Bytes)
|
|
||||||
|
|
||||||
if tt.transport == nil {
|
|
||||||
assert.NotNil(t, blt.Transport)
|
|
||||||
assert.NotEqual(t, http.DefaultTransport, blt.Transport)
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, tt.transport, blt.Transport)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestByteLimitedTransport_RoundTrip(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
responseBody string
|
|
||||||
limit int64
|
|
||||||
expectedError error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "under limit",
|
|
||||||
responseBody: "small response",
|
|
||||||
limit: 100,
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "exceeds limit",
|
|
||||||
responseBody: "this response will exceed the byte limit",
|
|
||||||
limit: 10,
|
|
||||||
expectedError: errBytesLimitExceeded,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
mockResp := &http.Response{
|
|
||||||
Body: io.NopCloser(bytes.NewBufferString(tt.responseBody)),
|
|
||||||
}
|
|
||||||
mockTransport := &mockTransport{response: mockResp}
|
|
||||||
|
|
||||||
blt := NewByteLimitedTransport(mockTransport, tt.limit)
|
|
||||||
resp, err := blt.RoundTrip(&http.Request{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer func() {
|
|
||||||
closeErr := resp.Body.Close()
|
|
||||||
assert.NoError(t, closeErr, "failed to close response body")
|
|
||||||
}()
|
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
|
||||||
if tt.expectedError != nil {
|
|
||||||
assert.True(t, errors.Is(err, tt.expectedError), "expected error %v, got %v", tt.expectedError, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.responseBody, string(data))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestByteLimitedReader_Close(t *testing.T) {
|
|
||||||
mockBody := io.NopCloser(bytes.NewBufferString("test"))
|
|
||||||
var byteCount int64
|
|
||||||
reader := &byteLimitedReader{
|
|
||||||
reader: mockBody,
|
|
||||||
limit: 100,
|
|
||||||
bytes: &byteCount,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := reader.Close()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestByteLimitedReader_AtomicCounting(t *testing.T) {
|
|
||||||
var byteCount int64
|
|
||||||
reader := &byteLimitedReader{
|
|
||||||
reader: io.NopCloser(bytes.NewBufferString("test data")),
|
|
||||||
limit: 5,
|
|
||||||
bytes: &byteCount,
|
|
||||||
}
|
|
||||||
|
|
||||||
// First read should succeed
|
|
||||||
buf := make([]byte, 4)
|
|
||||||
n, err := reader.Read(buf)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, 4, n)
|
|
||||||
|
|
||||||
// Second read should fail due to limit
|
|
||||||
n, err = reader.Read(buf)
|
|
||||||
assert.True(t, errors.Is(err, errBytesLimitExceeded), "expected error %v, got %v", errBytesLimitExceeded, err)
|
|
||||||
assert.Equal(t, 0, n)
|
|
||||||
|
|
||||||
// Verify atomic counter
|
|
||||||
assert.Greater(t, atomic.LoadInt64(&byteCount), int64(5))
|
|
||||||
}
|
|
||||||
@@ -1,261 +0,0 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
|
||||||
|
|
||||||
package gogit
|
|
||||||
|
|
||||||
import (
|
|
||||||
billy "github.com/go-git/go-billy/v5"
|
|
||||||
git "github.com/go-git/go-git/v5"
|
|
||||||
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
|
||||||
|
|
||||||
plumbing "github.com/go-git/go-git/v5/plumbing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockWorktree is an autogenerated mock type for the Worktree type
|
|
||||||
type MockWorktree struct {
|
|
||||||
mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockWorktree_Expecter struct {
|
|
||||||
mock *mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_m *MockWorktree) EXPECT() *MockWorktree_Expecter {
|
|
||||||
return &MockWorktree_Expecter{mock: &_m.Mock}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add provides a mock function with given fields: path
|
|
||||||
func (_m *MockWorktree) Add(path string) (plumbing.Hash, error) {
|
|
||||||
ret := _m.Called(path)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for Add")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 plumbing.Hash
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func(string) (plumbing.Hash, error)); ok {
|
|
||||||
return rf(path)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(string) plumbing.Hash); ok {
|
|
||||||
r0 = rf(path)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).(plumbing.Hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
|
||||||
r1 = rf(path)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockWorktree_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add'
|
|
||||||
type MockWorktree_Add_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add is a helper method to define mock.On call
|
|
||||||
// - path string
|
|
||||||
func (_e *MockWorktree_Expecter) Add(path interface{}) *MockWorktree_Add_Call {
|
|
||||||
return &MockWorktree_Add_Call{Call: _e.mock.On("Add", path)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWorktree_Add_Call) Run(run func(path string)) *MockWorktree_Add_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(string))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWorktree_Add_Call) Return(_a0 plumbing.Hash, _a1 error) *MockWorktree_Add_Call {
|
|
||||||
_c.Call.Return(_a0, _a1)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWorktree_Add_Call) RunAndReturn(run func(string) (plumbing.Hash, error)) *MockWorktree_Add_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit provides a mock function with given fields: message, opts
|
|
||||||
func (_m *MockWorktree) Commit(message string, opts *git.CommitOptions) (plumbing.Hash, error) {
|
|
||||||
ret := _m.Called(message, opts)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for Commit")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 plumbing.Hash
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func(string, *git.CommitOptions) (plumbing.Hash, error)); ok {
|
|
||||||
return rf(message, opts)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(string, *git.CommitOptions) plumbing.Hash); ok {
|
|
||||||
r0 = rf(message, opts)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).(plumbing.Hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(string, *git.CommitOptions) error); ok {
|
|
||||||
r1 = rf(message, opts)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockWorktree_Commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Commit'
|
|
||||||
type MockWorktree_Commit_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit is a helper method to define mock.On call
|
|
||||||
// - message string
|
|
||||||
// - opts *git.CommitOptions
|
|
||||||
func (_e *MockWorktree_Expecter) Commit(message interface{}, opts interface{}) *MockWorktree_Commit_Call {
|
|
||||||
return &MockWorktree_Commit_Call{Call: _e.mock.On("Commit", message, opts)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWorktree_Commit_Call) Run(run func(message string, opts *git.CommitOptions)) *MockWorktree_Commit_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(string), args[1].(*git.CommitOptions))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWorktree_Commit_Call) Return(_a0 plumbing.Hash, _a1 error) *MockWorktree_Commit_Call {
|
|
||||||
_c.Call.Return(_a0, _a1)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWorktree_Commit_Call) RunAndReturn(run func(string, *git.CommitOptions) (plumbing.Hash, error)) *MockWorktree_Commit_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filesystem provides a mock function with no fields
|
|
||||||
func (_m *MockWorktree) Filesystem() billy.Filesystem {
|
|
||||||
ret := _m.Called()
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for Filesystem")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 billy.Filesystem
|
|
||||||
if rf, ok := ret.Get(0).(func() billy.Filesystem); ok {
|
|
||||||
r0 = rf()
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).(billy.Filesystem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockWorktree_Filesystem_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Filesystem'
|
|
||||||
type MockWorktree_Filesystem_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filesystem is a helper method to define mock.On call
|
|
||||||
func (_e *MockWorktree_Expecter) Filesystem() *MockWorktree_Filesystem_Call {
|
|
||||||
return &MockWorktree_Filesystem_Call{Call: _e.mock.On("Filesystem")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWorktree_Filesystem_Call) Run(run func()) *MockWorktree_Filesystem_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run()
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWorktree_Filesystem_Call) Return(_a0 billy.Filesystem) *MockWorktree_Filesystem_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWorktree_Filesystem_Call) RunAndReturn(run func() billy.Filesystem) *MockWorktree_Filesystem_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove provides a mock function with given fields: path
|
|
||||||
func (_m *MockWorktree) Remove(path string) (plumbing.Hash, error) {
|
|
||||||
ret := _m.Called(path)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for Remove")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 plumbing.Hash
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func(string) (plumbing.Hash, error)); ok {
|
|
||||||
return rf(path)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(string) plumbing.Hash); ok {
|
|
||||||
r0 = rf(path)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).(plumbing.Hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
|
||||||
r1 = rf(path)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockWorktree_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove'
|
|
||||||
type MockWorktree_Remove_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove is a helper method to define mock.On call
|
|
||||||
// - path string
|
|
||||||
func (_e *MockWorktree_Expecter) Remove(path interface{}) *MockWorktree_Remove_Call {
|
|
||||||
return &MockWorktree_Remove_Call{Call: _e.mock.On("Remove", path)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWorktree_Remove_Call) Run(run func(path string)) *MockWorktree_Remove_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(string))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWorktree_Remove_Call) Return(_a0 plumbing.Hash, _a1 error) *MockWorktree_Remove_Call {
|
|
||||||
_c.Call.Return(_a0, _a1)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *MockWorktree_Remove_Call) RunAndReturn(run func(string) (plumbing.Hash, error)) *MockWorktree_Remove_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockWorktree creates a new instance of MockWorktree. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
|
||||||
// The first argument is typically a *testing.T value.
|
|
||||||
func NewMockWorktree(t interface {
|
|
||||||
mock.TestingT
|
|
||||||
Cleanup(func())
|
|
||||||
}) *MockWorktree {
|
|
||||||
mock := &MockWorktree{}
|
|
||||||
mock.Mock.Test(t)
|
|
||||||
|
|
||||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
|
||||||
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
@@ -1,468 +0,0 @@
|
|||||||
package gogit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5"
|
|
||||||
"github.com/go-git/go-billy/v5/util"
|
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport/client"
|
|
||||||
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
||||||
|
|
||||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
|
||||||
"github.com/grafana/grafana/pkg/util/httpclient"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// maxOperationBytes is the maximum size of a git operation in bytes (1 GB)
|
|
||||||
maxOperationBytes = int64(1 << 30)
|
|
||||||
maxOperationTimeout = 10 * time.Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Create a size-limited writer that will cancel the context if size is exceeded
|
|
||||||
limitedTransport := NewByteLimitedTransport(httpclient.NewHTTPTransport(), maxOperationBytes)
|
|
||||||
httpClient := githttp.NewClient(&http.Client{
|
|
||||||
Transport: limitedTransport,
|
|
||||||
})
|
|
||||||
client.InstallProtocol("https", httpClient)
|
|
||||||
client.InstallProtocol("http", httpClient)
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:generate mockery --name=Worktree --output=mocks --inpackage --filename=worktree_mock.go --with-expecter
|
|
||||||
type Worktree interface {
|
|
||||||
Commit(message string, opts *git.CommitOptions) (plumbing.Hash, error)
|
|
||||||
Remove(path string) (plumbing.Hash, error)
|
|
||||||
Add(path string) (plumbing.Hash, error)
|
|
||||||
Filesystem() billy.Filesystem
|
|
||||||
}
|
|
||||||
|
|
||||||
type worktree struct {
|
|
||||||
*git.Worktree
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:generate mockery --name=Repository --output=mocks --inpackage --filename=repository_mock.go --with-expecter
|
|
||||||
type Repository interface {
|
|
||||||
PushContext(ctx context.Context, o *git.PushOptions) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *worktree) Filesystem() billy.Filesystem {
|
|
||||||
return w.Worktree.Filesystem
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ repository.Repository = (*GoGitRepo)(nil)
|
|
||||||
|
|
||||||
type GoGitRepo struct {
|
|
||||||
config *provisioning.Repository
|
|
||||||
decryptedPassword string
|
|
||||||
opts repository.CloneOptions
|
|
||||||
|
|
||||||
repo Repository
|
|
||||||
tree Worktree
|
|
||||||
dir string // file path to worktree root (necessary? should use billy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will create a new clone every time
|
|
||||||
// As structured, it is valid for one context and should not be shared across multiple requests
|
|
||||||
func Clone(
|
|
||||||
ctx context.Context,
|
|
||||||
root string,
|
|
||||||
config *provisioning.Repository,
|
|
||||||
opts repository.CloneOptions,
|
|
||||||
secrets secrets.Service,
|
|
||||||
) (repository.ClonedRepository, error) {
|
|
||||||
if root == "" {
|
|
||||||
return nil, fmt.Errorf("missing root config")
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Namespace == "" {
|
|
||||||
return nil, fmt.Errorf("config is missing namespace")
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Name == "" {
|
|
||||||
return nil, fmt.Errorf("config is missing name")
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.BeforeFn != nil {
|
|
||||||
if err := opts.BeforeFn(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add a timeout to the operation
|
|
||||||
timeout := maxOperationTimeout
|
|
||||||
if opts.Timeout > 0 {
|
|
||||||
timeout = opts.Timeout
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
decrypted, err := secrets.Decrypt(ctx, config.Spec.GitHub.EncryptedToken)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error decrypting token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(root, 0700); err != nil {
|
|
||||||
return nil, fmt.Errorf("create root dir: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, err := os.MkdirTemp(root, fmt.Sprintf("clone-%s-%s-", config.Namespace, config.Name))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create temp clone dir: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
progress := opts.Progress
|
|
||||||
if progress == nil {
|
|
||||||
progress = io.Discard
|
|
||||||
}
|
|
||||||
|
|
||||||
repo, tree, err := clone(ctx, config, opts, decrypted, dir, progress)
|
|
||||||
if err != nil {
|
|
||||||
if err := os.RemoveAll(dir); err != nil {
|
|
||||||
return nil, fmt.Errorf("remove temp clone dir after clone failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("clone: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &GoGitRepo{
|
|
||||||
config: config,
|
|
||||||
tree: &worktree{Worktree: tree},
|
|
||||||
opts: opts,
|
|
||||||
decryptedPassword: string(decrypted),
|
|
||||||
repo: repo,
|
|
||||||
dir: dir,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func clone(ctx context.Context, config *provisioning.Repository, opts repository.CloneOptions, decrypted []byte, dir string, progress io.Writer) (*git.Repository, *git.Worktree, error) {
|
|
||||||
gitcfg := config.Spec.GitHub
|
|
||||||
url := gitcfg.URL
|
|
||||||
if !strings.HasPrefix(url, "file://") {
|
|
||||||
url = fmt.Sprintf("%s.git", url)
|
|
||||||
}
|
|
||||||
|
|
||||||
branch := plumbing.NewBranchReferenceName(gitcfg.Branch)
|
|
||||||
cloneOpts := &git.CloneOptions{
|
|
||||||
ReferenceName: branch,
|
|
||||||
Auth: &githttp.BasicAuth{
|
|
||||||
Username: "grafana", // this can be anything except an empty string for PAT
|
|
||||||
Password: string(decrypted), // TODO... will need to get from a service!
|
|
||||||
},
|
|
||||||
URL: url,
|
|
||||||
Progress: progress,
|
|
||||||
}
|
|
||||||
|
|
||||||
repo, err := git.PlainCloneContext(ctx, dir, false, cloneOpts)
|
|
||||||
if errors.Is(err, plumbing.ErrReferenceNotFound) && opts.CreateIfNotExists {
|
|
||||||
cloneOpts.ReferenceName = "" // empty
|
|
||||||
repo, err = git.PlainCloneContext(ctx, dir, false, cloneOpts)
|
|
||||||
if err == nil {
|
|
||||||
worktree, err := repo.Worktree()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
err = worktree.Checkout(&git.CheckoutOptions{
|
|
||||||
Branch: branch,
|
|
||||||
Force: true,
|
|
||||||
Create: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to create new branch: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("clone error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rcfg, err := repo.Config()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("error reading repository config %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
origin := rcfg.Remotes["origin"]
|
|
||||||
if origin == nil {
|
|
||||||
return nil, nil, fmt.Errorf("missing origin remote %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if url != origin.URLs[0] {
|
|
||||||
return nil, nil, fmt.Errorf("unexpected remote (expected: %s, found: %s)", url, origin.URLs[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
worktree, err := repo.Worktree()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("get worktree: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo, worktree, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// After making changes to the worktree, push changes
|
|
||||||
func (g *GoGitRepo) Push(ctx context.Context, opts repository.PushOptions) error {
|
|
||||||
timeout := maxOperationTimeout
|
|
||||||
if opts.Timeout > 0 {
|
|
||||||
timeout = opts.Timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
progress := opts.Progress
|
|
||||||
if progress == nil {
|
|
||||||
progress = io.Discard
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.BeforeFn != nil {
|
|
||||||
if err := opts.BeforeFn(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if !g.opts.PushOnWrites {
|
|
||||||
_, err := g.tree.Commit("exported from grafana", &git.CommitOptions{
|
|
||||||
All: true, // Add everything that changed
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// empty commit is fine -- no change
|
|
||||||
if !errors.Is(err, git.ErrEmptyCommit) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := g.repo.PushContext(ctx, &git.PushOptions{
|
|
||||||
Progress: progress,
|
|
||||||
Force: true, // avoid fast-forward-errors
|
|
||||||
Auth: &githttp.BasicAuth{ // reuse logic from clone?
|
|
||||||
Username: "grafana",
|
|
||||||
Password: g.decryptedPassword,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if errors.Is(err, git.NoErrAlreadyUpToDate) {
|
|
||||||
return nil // same as the target
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GoGitRepo) Remove(ctx context.Context) error {
|
|
||||||
return os.RemoveAll(g.dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config implements repository.Repository.
|
|
||||||
func (g *GoGitRepo) Config() *provisioning.Repository {
|
|
||||||
return g.config
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadTree implements repository.Repository.
|
|
||||||
func (g *GoGitRepo) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
|
||||||
var treePath string
|
|
||||||
if g.config.Spec.GitHub.Path != "" {
|
|
||||||
treePath = g.config.Spec.GitHub.Path
|
|
||||||
}
|
|
||||||
treePath = safepath.Clean(treePath)
|
|
||||||
|
|
||||||
entries := make([]repository.FileTreeEntry, 0, 100)
|
|
||||||
err := util.Walk(g.tree.Filesystem(), treePath, func(path string, info fs.FileInfo, err error) error {
|
|
||||||
// We already have an error, just pass it onwards.
|
|
||||||
if err != nil ||
|
|
||||||
// This is the root of the repository (or should pretend to be)
|
|
||||||
safepath.Clean(path) == "" || path == treePath ||
|
|
||||||
// This is the Git data
|
|
||||||
(treePath == "" && (strings.HasPrefix(path, ".git/") || path == ".git")) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if treePath != "" {
|
|
||||||
path = strings.TrimPrefix(path, treePath)
|
|
||||||
}
|
|
||||||
entry := repository.FileTreeEntry{
|
|
||||||
Path: strings.TrimLeft(path, "/"),
|
|
||||||
Size: info.Size(),
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
entry.Blob = true
|
|
||||||
// For a real instance, this will likely be based on:
|
|
||||||
// https://github.com/go-git/go-git/blob/main/_examples/ls/main.go#L25
|
|
||||||
entry.Hash = fmt.Sprintf("TODO/%d", info.Size()) // but not used for
|
|
||||||
}
|
|
||||||
entries = append(entries, entry)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
|
||||||
// We intentionally ignore this case, as it is expected
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, fmt.Errorf("walk tree for ref '%s': %w", ref, err)
|
|
||||||
}
|
|
||||||
return entries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GoGitRepo) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
|
||||||
return &provisioning.TestResults{
|
|
||||||
Success: g.tree != nil,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update implements repository.Repository.
|
|
||||||
func (g *GoGitRepo) Update(ctx context.Context, path string, ref string, data []byte, message string) error {
|
|
||||||
return g.Write(ctx, path, ref, data, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create implements repository.Repository.
|
|
||||||
func (g *GoGitRepo) Create(ctx context.Context, path string, ref string, data []byte, message string) error {
|
|
||||||
// FIXME: this means we would override files
|
|
||||||
return g.Write(ctx, path, ref, data, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements repository.Repository.
|
|
||||||
func (g *GoGitRepo) Write(ctx context.Context, fpath string, ref string, data []byte, message string) error {
|
|
||||||
if err := verifyPathWithoutRef(fpath, ref); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fpath = safepath.Join(g.config.Spec.GitHub.Path, fpath)
|
|
||||||
|
|
||||||
// FIXME: this means that won't export empty folders
|
|
||||||
// should we create them with a .keep file?
|
|
||||||
// For folders, just create the folder and ignore the commit
|
|
||||||
if safepath.IsDir(fpath) {
|
|
||||||
return g.tree.Filesystem().MkdirAll(fpath, 0750)
|
|
||||||
}
|
|
||||||
|
|
||||||
dir := safepath.Dir(fpath)
|
|
||||||
if dir != "" {
|
|
||||||
err := g.tree.Filesystem().MkdirAll(dir, 0750)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := g.tree.Filesystem().Create(fpath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = file.Write(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = g.tree.Add(fpath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return g.maybeCommit(ctx, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GoGitRepo) maybeCommit(ctx context.Context, message string) error {
|
|
||||||
// Skip commit for each file
|
|
||||||
if !g.opts.PushOnWrites {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := &git.CommitOptions{
|
|
||||||
Author: &object.Signature{
|
|
||||||
Name: "grafana",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
sig := repository.GetAuthorSignature(ctx)
|
|
||||||
if sig != nil && sig.Name != "" {
|
|
||||||
opts.Author.Name = sig.Name
|
|
||||||
opts.Author.Email = sig.Email
|
|
||||||
opts.Author.When = sig.When
|
|
||||||
}
|
|
||||||
if opts.Author.When.IsZero() {
|
|
||||||
opts.Author.When = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := g.tree.Commit(message, opts)
|
|
||||||
if errors.Is(err, git.ErrEmptyCommit) {
|
|
||||||
return nil // empty commit is fine -- no change
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete implements repository.Repository.
|
|
||||||
func (g *GoGitRepo) Delete(ctx context.Context, fpath string, ref string, message string) error {
|
|
||||||
if err := verifyPathWithoutRef(fpath, ref); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fpath = safepath.Join(g.config.Spec.GitHub.Path, fpath)
|
|
||||||
if _, err := g.tree.Remove(fpath); err != nil {
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return repository.ErrFileNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return g.maybeCommit(ctx, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements repository.Repository.
|
|
||||||
func (g *GoGitRepo) Read(ctx context.Context, path string, ref string) (*repository.FileInfo, error) {
|
|
||||||
if err := verifyPathWithoutRef(path, ref); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
readPath := safepath.Join(g.config.Spec.GitHub.Path, path)
|
|
||||||
stat, err := g.tree.Filesystem().Lstat(readPath)
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return nil, repository.ErrFileNotFound
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, fmt.Errorf("stat path '%s': %w", readPath, err)
|
|
||||||
}
|
|
||||||
info := &repository.FileInfo{
|
|
||||||
Path: path,
|
|
||||||
Modified: &metav1.Time{
|
|
||||||
Time: stat.ModTime(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if !stat.IsDir() {
|
|
||||||
f, err := g.tree.Filesystem().Open(readPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("open file '%s': %w", readPath, err)
|
|
||||||
}
|
|
||||||
info.Data, err = io.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("read file '%s': %w", readPath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyPathWithoutRef(path string, ref string) error {
|
|
||||||
if path == "" {
|
|
||||||
return fmt.Errorf("expected path")
|
|
||||||
}
|
|
||||||
if ref != "" {
|
|
||||||
return fmt.Errorf("ref unsupported")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// History implements repository.Repository.
|
|
||||||
func (g *GoGitRepo) History(ctx context.Context, path string, ref string) ([]provisioning.HistoryItem, error) {
|
|
||||||
return nil, &apierrors.StatusError{
|
|
||||||
ErrStatus: metav1.Status{
|
|
||||||
Message: "history is not yet implemented",
|
|
||||||
Code: http.StatusNotImplemented,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate implements repository.Repository.
|
|
||||||
func (g *GoGitRepo) Validate() field.ErrorList {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
+29
-19
@@ -1,4 +1,4 @@
|
|||||||
package repository
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
|
||||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
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/safepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -75,9 +76,9 @@ func (r *LocalFolderResolver) LocalPath(p string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ Repository = (*localRepository)(nil)
|
_ repository.Repository = (*localRepository)(nil)
|
||||||
_ Writer = (*localRepository)(nil)
|
_ repository.Writer = (*localRepository)(nil)
|
||||||
_ Reader = (*localRepository)(nil)
|
_ repository.Reader = (*localRepository)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
type localRepository struct {
|
type localRepository struct {
|
||||||
@@ -147,17 +148,17 @@ func (r *localRepository) Validate() field.ErrorList {
|
|||||||
func (r *localRepository) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
func (r *localRepository) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
||||||
path := field.NewPath("spec", "local", "path")
|
path := field.NewPath("spec", "local", "path")
|
||||||
if r.config.Spec.Local.Path == "" {
|
if r.config.Spec.Local.Path == "" {
|
||||||
return fromFieldError(field.Required(path, "no path is configured")), nil
|
return repository.FromFieldError(field.Required(path, "no path is configured")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := r.resolver.LocalPath(r.config.Spec.Local.Path)
|
_, err := r.resolver.LocalPath(r.config.Spec.Local.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fromFieldError(field.Invalid(path, r.config.Spec.Local.Path, err.Error())), nil
|
return repository.FromFieldError(field.Invalid(path, r.config.Spec.Local.Path, err.Error())), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = os.Stat(r.path)
|
_, err = os.Stat(r.path)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
return fromFieldError(field.NotFound(path, r.config.Spec.Local.Path)), nil
|
return repository.FromFieldError(field.NotFound(path, r.config.Spec.Local.Path)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &provisioning.TestResults{
|
return &provisioning.TestResults{
|
||||||
@@ -176,7 +177,7 @@ func (r *localRepository) validateRequest(ref string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadResource implements provisioning.Repository.
|
// ReadResource implements provisioning.Repository.
|
||||||
func (r *localRepository) Read(ctx context.Context, filePath string, ref string) (*FileInfo, error) {
|
func (r *localRepository) Read(ctx context.Context, filePath string, ref string) (*repository.FileInfo, error) {
|
||||||
if err := r.validateRequest(ref); err != nil {
|
if err := r.validateRequest(ref); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -184,13 +185,13 @@ func (r *localRepository) Read(ctx context.Context, filePath string, ref string)
|
|||||||
actualPath := safepath.Join(r.path, filePath)
|
actualPath := safepath.Join(r.path, filePath)
|
||||||
info, err := os.Stat(actualPath)
|
info, err := os.Stat(actualPath)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
return nil, ErrFileNotFound
|
return nil, repository.ErrFileNotFound
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, fmt.Errorf("stat file: %w", err)
|
return nil, fmt.Errorf("stat file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
return &FileInfo{
|
return &repository.FileInfo{
|
||||||
Path: filePath,
|
Path: filePath,
|
||||||
Modified: &metav1.Time{
|
Modified: &metav1.Time{
|
||||||
Time: info.ModTime(),
|
Time: info.ModTime(),
|
||||||
@@ -209,7 +210,7 @@ func (r *localRepository) Read(ctx context.Context, filePath string, ref string)
|
|||||||
return nil, fmt.Errorf("calculate hash of file: %w", err)
|
return nil, fmt.Errorf("calculate hash of file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &FileInfo{
|
return &repository.FileInfo{
|
||||||
Path: filePath,
|
Path: filePath,
|
||||||
Data: data,
|
Data: data,
|
||||||
Hash: hash,
|
Hash: hash,
|
||||||
@@ -220,7 +221,7 @@ func (r *localRepository) Read(ctx context.Context, filePath string, ref string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadResource implements provisioning.Repository.
|
// ReadResource implements provisioning.Repository.
|
||||||
func (r *localRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) {
|
func (r *localRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
||||||
if err := r.validateRequest(ref); err != nil {
|
if err := r.validateRequest(ref); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -228,16 +229,16 @@ func (r *localRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeE
|
|||||||
// Return an empty list when folder does not exist
|
// Return an empty list when folder does not exist
|
||||||
_, err := os.Stat(r.path)
|
_, err := os.Stat(r.path)
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
return []FileTreeEntry{}, nil
|
return []repository.FileTreeEntry{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rootlen := len(r.path)
|
rootlen := len(r.path)
|
||||||
entries := make([]FileTreeEntry, 0, 100)
|
entries := make([]repository.FileTreeEntry, 0, 100)
|
||||||
err = filepath.Walk(r.path, func(path string, info fs.FileInfo, err error) error {
|
err = filepath.Walk(r.path, func(path string, info fs.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
entry := FileTreeEntry{
|
entry := repository.FileTreeEntry{
|
||||||
Path: strings.TrimLeft(path[rootlen:], "/"),
|
Path: strings.TrimLeft(path[rootlen:], "/"),
|
||||||
Size: info.Size(),
|
Size: info.Size(),
|
||||||
}
|
}
|
||||||
@@ -251,8 +252,10 @@ func (r *localRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeE
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("read and calculate hash of path %s: %w", path, err)
|
return fmt.Errorf("read and calculate hash of path %s: %w", path, err)
|
||||||
}
|
}
|
||||||
|
} else if !strings.HasSuffix(entry.Path, "/") {
|
||||||
|
// ensure trailing slash for directories
|
||||||
|
entry.Path = entry.Path + "/"
|
||||||
}
|
}
|
||||||
// TODO: do folders have a trailing slash?
|
|
||||||
entries = append(entries, entry)
|
entries = append(entries, entry)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -263,7 +266,7 @@ func (r *localRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeE
|
|||||||
func (r *localRepository) calculateFileHash(path string) (string, int64, error) {
|
func (r *localRepository) calculateFileHash(path string) (string, int64, error) {
|
||||||
// Treats https://securego.io/docs/rules/g304.html
|
// Treats https://securego.io/docs/rules/g304.html
|
||||||
if !safepath.InDir(path, r.path) {
|
if !safepath.InDir(path, r.path) {
|
||||||
return "", 0, ErrFileNotFound
|
return "", 0, repository.ErrFileNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've already made sure the path is safe, so we'll ignore the gosec lint.
|
// We've already made sure the path is safe, so we'll ignore the gosec lint.
|
||||||
@@ -329,7 +332,7 @@ func (r *localRepository) Update(ctx context.Context, path string, ref string, d
|
|||||||
|
|
||||||
f, err := os.Stat(path)
|
f, err := os.Stat(path)
|
||||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||||
return ErrFileNotFound
|
return repository.ErrFileNotFound
|
||||||
}
|
}
|
||||||
if f.IsDir() {
|
if f.IsDir() {
|
||||||
return apierrors.NewBadRequest("path exists but it is a directory")
|
return apierrors.NewBadRequest("path exists but it is a directory")
|
||||||
@@ -360,5 +363,12 @@ func (r *localRepository) Delete(ctx context.Context, path string, ref string, c
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.Remove(safepath.Join(r.path, path))
|
fullPath := safepath.Join(r.path, path)
|
||||||
|
|
||||||
|
if safepath.IsDir(path) {
|
||||||
|
// if it is a folder, delete all of its contents
|
||||||
|
return os.RemoveAll(fullPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Remove(fullPath)
|
||||||
}
|
}
|
||||||
+52
-16
@@ -1,4 +1,4 @@
|
|||||||
package repository
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
|
||||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLocalResolver(t *testing.T) {
|
func TestLocalResolver(t *testing.T) {
|
||||||
@@ -91,14 +92,14 @@ func TestLocalResolver(t *testing.T) {
|
|||||||
|
|
||||||
// Verify all directories and files are present
|
// Verify all directories and files are present
|
||||||
expectedPaths := []string{
|
expectedPaths := []string{
|
||||||
"another",
|
"another/",
|
||||||
"another/path",
|
"another/path/",
|
||||||
"another/path/file.txt",
|
"another/path/file.txt",
|
||||||
"level1",
|
"level1/",
|
||||||
"level1/file1.txt",
|
"level1/file1.txt",
|
||||||
"level1/level2",
|
"level1/level2/",
|
||||||
"level1/level2/file2.txt",
|
"level1/level2/file2.txt",
|
||||||
"level1/level2/level3",
|
"level1/level2/level3/",
|
||||||
"level1/level2/level3/file3.txt",
|
"level1/level2/level3/file3.txt",
|
||||||
"root.txt",
|
"root.txt",
|
||||||
}
|
}
|
||||||
@@ -542,6 +543,41 @@ func TestLocalRepository_Delete(t *testing.T) {
|
|||||||
comment: "test delete with ref",
|
comment: "test delete with ref",
|
||||||
expectedErr: apierrors.NewBadRequest("local repository does not support ref"),
|
expectedErr: apierrors.NewBadRequest("local repository does not support ref"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "delete folder with nested files",
|
||||||
|
setup: func(t *testing.T) (string, *localRepository) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
nestedFolderPath := filepath.Join(tempDir, "folder")
|
||||||
|
err := os.MkdirAll(nestedFolderPath, 0700)
|
||||||
|
require.NoError(t, err)
|
||||||
|
subFolderPath := filepath.Join(nestedFolderPath, "nested-folder")
|
||||||
|
err = os.MkdirAll(subFolderPath, 0700)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = os.WriteFile(filepath.Join(nestedFolderPath, "nested-dash.txt"), []byte("content1"), 0600)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Create repository with the temp directory as permitted prefix
|
||||||
|
repo := &localRepository{
|
||||||
|
config: &provisioning.Repository{
|
||||||
|
Spec: provisioning.RepositorySpec{
|
||||||
|
Local: &provisioning.LocalRepositoryConfig{
|
||||||
|
Path: tempDir,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolver: &LocalFolderResolver{
|
||||||
|
PermittedPrefixes: []string{tempDir},
|
||||||
|
},
|
||||||
|
path: tempDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempDir, repo
|
||||||
|
},
|
||||||
|
path: "folder/",
|
||||||
|
ref: "",
|
||||||
|
comment: "test delete folder with nested content",
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -668,7 +704,7 @@ func TestLocalRepository_Update(t *testing.T) {
|
|||||||
ref: "",
|
ref: "",
|
||||||
data: []byte("content"),
|
data: []byte("content"),
|
||||||
comment: "",
|
comment: "",
|
||||||
expectedErr: ErrFileNotFound,
|
expectedErr: repository.ErrFileNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "update directory",
|
name: "update directory",
|
||||||
@@ -1139,7 +1175,7 @@ func TestLocalRepository_Read(t *testing.T) {
|
|||||||
path string
|
path string
|
||||||
ref string
|
ref string
|
||||||
expectedErr error
|
expectedErr error
|
||||||
expected *FileInfo
|
expected *repository.FileInfo
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "read existing file",
|
name: "read existing file",
|
||||||
@@ -1168,7 +1204,7 @@ func TestLocalRepository_Read(t *testing.T) {
|
|||||||
return tempDir, repo
|
return tempDir, repo
|
||||||
},
|
},
|
||||||
path: "test-file.txt",
|
path: "test-file.txt",
|
||||||
expected: &FileInfo{
|
expected: &repository.FileInfo{
|
||||||
Path: "test-file.txt",
|
Path: "test-file.txt",
|
||||||
Modified: &metav1.Time{Time: time.Now()},
|
Modified: &metav1.Time{Time: time.Now()},
|
||||||
Data: []byte("test content"),
|
Data: []byte("test content"),
|
||||||
@@ -1196,7 +1232,7 @@ func TestLocalRepository_Read(t *testing.T) {
|
|||||||
return tempDir, repo
|
return tempDir, repo
|
||||||
},
|
},
|
||||||
path: "non-existent-file.txt",
|
path: "non-existent-file.txt",
|
||||||
expectedErr: ErrFileNotFound,
|
expectedErr: repository.ErrFileNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "read with ref should fail",
|
name: "read with ref should fail",
|
||||||
@@ -1254,7 +1290,7 @@ func TestLocalRepository_Read(t *testing.T) {
|
|||||||
return tempDir, repo
|
return tempDir, repo
|
||||||
},
|
},
|
||||||
path: "test-dir",
|
path: "test-dir",
|
||||||
expected: &FileInfo{
|
expected: &repository.FileInfo{
|
||||||
Path: "test-dir",
|
Path: "test-dir",
|
||||||
Modified: &metav1.Time{Time: time.Now()},
|
Modified: &metav1.Time{Time: time.Now()},
|
||||||
},
|
},
|
||||||
@@ -1292,7 +1328,7 @@ func TestLocalRepository_ReadTree(t *testing.T) {
|
|||||||
setup func(t *testing.T) (string, *localRepository)
|
setup func(t *testing.T) (string, *localRepository)
|
||||||
ref string
|
ref string
|
||||||
expectedErr error
|
expectedErr error
|
||||||
expected []FileTreeEntry
|
expected []repository.FileTreeEntry
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "read empty directory",
|
name: "read empty directory",
|
||||||
@@ -1314,7 +1350,7 @@ func TestLocalRepository_ReadTree(t *testing.T) {
|
|||||||
|
|
||||||
return tempDir, repo
|
return tempDir, repo
|
||||||
},
|
},
|
||||||
expected: []FileTreeEntry{},
|
expected: []repository.FileTreeEntry{},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1344,10 +1380,10 @@ func TestLocalRepository_ReadTree(t *testing.T) {
|
|||||||
|
|
||||||
return tempDir, repo
|
return tempDir, repo
|
||||||
},
|
},
|
||||||
expected: []FileTreeEntry{
|
expected: []repository.FileTreeEntry{
|
||||||
{Path: "file1.txt", Blob: true, Size: 8},
|
{Path: "file1.txt", Blob: true, Size: 8},
|
||||||
{Path: "file2.txt", Blob: true, Size: 8},
|
{Path: "file2.txt", Blob: true, Size: 8},
|
||||||
{Path: "subdir", Blob: false},
|
{Path: "subdir/", Blob: false},
|
||||||
{Path: "subdir/file3.txt", Blob: true, Size: 8},
|
{Path: "subdir/file3.txt", Blob: true, Size: 8},
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
@@ -1397,7 +1433,7 @@ func TestLocalRepository_ReadTree(t *testing.T) {
|
|||||||
|
|
||||||
return tempDir, repo
|
return tempDir, repo
|
||||||
},
|
},
|
||||||
expected: []FileTreeEntry{},
|
expected: []repository.FileTreeEntry{},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package nanogit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
|
||||||
pgh "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
||||||
|
|
||||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// githubRepository is a repository implementation that integrates both a GitHub API-backed repository and a nanogit-based repository.
|
|
||||||
// It combines the features of the GitHub API with those of a standard Git repository.
|
|
||||||
// This is an interim solution to support both backends within a single repository abstraction.
|
|
||||||
// Once nanogit is fully integrated, functionality from GithubRepository should be migrated here, and this type should extend the nanogit.GitRepository interface.
|
|
||||||
type githubRepository struct {
|
|
||||||
apiRepo repository.GithubRepository
|
|
||||||
nanogitRepo repository.GitRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGithubRepository(
|
|
||||||
apiRepo repository.GithubRepository,
|
|
||||||
nanogitRepo repository.GitRepository,
|
|
||||||
) repository.GithubRepository {
|
|
||||||
return &githubRepository{
|
|
||||||
apiRepo: apiRepo,
|
|
||||||
nanogitRepo: nanogitRepo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Config() *provisioning.Repository {
|
|
||||||
return r.nanogitRepo.Config()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Owner() string {
|
|
||||||
return r.apiRepo.Owner()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Repo() string {
|
|
||||||
return r.apiRepo.Repo()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Client() pgh.Client {
|
|
||||||
return r.apiRepo.Client()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate extends the nanogit repo validation with github specific validation
|
|
||||||
func (r *githubRepository) Validate() (list field.ErrorList) {
|
|
||||||
cfg := r.nanogitRepo.Config()
|
|
||||||
gh := cfg.Spec.GitHub
|
|
||||||
if gh == nil {
|
|
||||||
list = append(list, field.Required(field.NewPath("spec", "github"), "a github config is required"))
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
if gh.URL == "" {
|
|
||||||
list = append(list, field.Required(field.NewPath("spec", "github", "url"), "a github url is required"))
|
|
||||||
} else {
|
|
||||||
_, _, err := repository.ParseOwnerRepoGithub(gh.URL)
|
|
||||||
if err != nil {
|
|
||||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, err.Error()))
|
|
||||||
} else if !strings.HasPrefix(gh.URL, "https://github.com/") {
|
|
||||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, "URL must start with https://github.com/"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(list) > 0 {
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.nanogitRepo.Validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test implements provisioning.Repository.
|
|
||||||
func (r *githubRepository) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
|
||||||
return r.apiRepo.Test(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadResource implements provisioning.Repository.
|
|
||||||
func (r *githubRepository) Read(ctx context.Context, filePath, ref string) (*repository.FileInfo, error) {
|
|
||||||
return r.nanogitRepo.Read(ctx, filePath, ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
|
|
||||||
return r.nanogitRepo.ReadTree(ctx, ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Create(ctx context.Context, path, ref string, data []byte, comment string) error {
|
|
||||||
return r.nanogitRepo.Create(ctx, path, ref, data, comment)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Update(ctx context.Context, path, ref string, data []byte, comment string) error {
|
|
||||||
return r.nanogitRepo.Update(ctx, path, ref, data, comment)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
|
|
||||||
return r.nanogitRepo.Write(ctx, path, ref, data, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Delete(ctx context.Context, path, ref, comment string) error {
|
|
||||||
return r.nanogitRepo.Delete(ctx, path, ref, comment)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) History(ctx context.Context, path, ref string) ([]provisioning.HistoryItem, error) {
|
|
||||||
// Github API provides avatar URLs which nanogit does not, so we delegate to the github repo.
|
|
||||||
return r.apiRepo.History(ctx, path, ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) LatestRef(ctx context.Context) (string, error) {
|
|
||||||
return r.nanogitRepo.LatestRef(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) CompareFiles(ctx context.Context, base, ref string) ([]repository.VersionedFileChange, error) {
|
|
||||||
return r.nanogitRepo.CompareFiles(ctx, base, ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceURLs implements RepositoryWithURLs.
|
|
||||||
func (r *githubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*provisioning.ResourceURLs, error) {
|
|
||||||
return r.apiRepo.ResourceURLs(ctx, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *githubRepository) Clone(ctx context.Context, opts repository.CloneOptions) (repository.ClonedRepository, error) {
|
|
||||||
return r.nanogitRepo.Clone(ctx, opts)
|
|
||||||
}
|
|
||||||
@@ -1,356 +0,0 @@
|
|||||||
package nanogit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
|
||||||
pgh "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGithubRepository(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
// Create a proper config for testing
|
|
||||||
expectedConfig := &provisioning.Repository{
|
|
||||||
Spec: provisioning.RepositorySpec{
|
|
||||||
Type: provisioning.GitHubRepositoryType,
|
|
||||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
||||||
URL: "https://github.com/test/repo",
|
|
||||||
Branch: "main",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up mock expectations for the methods that exist
|
|
||||||
gitRepo.EXPECT().Config().Return(expectedConfig)
|
|
||||||
apiRepo.EXPECT().Owner().Return("test")
|
|
||||||
apiRepo.EXPECT().Repo().Return("repo")
|
|
||||||
mockClient := pgh.NewMockClient(t)
|
|
||||||
apiRepo.EXPECT().Client().Return(mockClient)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
|
|
||||||
t.Run("delegates config to nanogit repo", func(t *testing.T) {
|
|
||||||
result := repo.Config()
|
|
||||||
require.Equal(t, expectedConfig, result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates owner to api repo", func(t *testing.T) {
|
|
||||||
result := repo.Owner()
|
|
||||||
require.Equal(t, "test", result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates repo to api repo", func(t *testing.T) {
|
|
||||||
result := repo.Repo()
|
|
||||||
require.Equal(t, "repo", result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates client to api repo", func(t *testing.T) {
|
|
||||||
result := repo.Client()
|
|
||||||
require.Equal(t, mockClient, result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGithubRepositoryDelegation(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("delegates test to api repo", func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
expectedResult := &provisioning.TestResults{
|
|
||||||
Code: 200,
|
|
||||||
Success: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
apiRepo.EXPECT().Test(ctx).Return(expectedResult, nil)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
result, err := repo.Test(ctx)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedResult, result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates read to nanogit repo", func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
expectedFileInfo := &repository.FileInfo{
|
|
||||||
Path: "test.yaml",
|
|
||||||
Data: []byte("test data"),
|
|
||||||
Ref: "main",
|
|
||||||
Hash: "abc123",
|
|
||||||
}
|
|
||||||
|
|
||||||
gitRepo.EXPECT().Read(ctx, "test.yaml", "main").Return(expectedFileInfo, nil)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
result, err := repo.Read(ctx, "test.yaml", "main")
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedFileInfo, result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates read tree to nanogit repo", func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
expectedEntries := []repository.FileTreeEntry{
|
|
||||||
{Path: "file1.yaml", Size: 100, Hash: "hash1", Blob: true},
|
|
||||||
{Path: "dir/", Size: 0, Hash: "hash2", Blob: false},
|
|
||||||
}
|
|
||||||
|
|
||||||
gitRepo.EXPECT().ReadTree(ctx, "main").Return(expectedEntries, nil)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
result, err := repo.ReadTree(ctx, "main")
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedEntries, result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates create to nanogit repo", func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
data := []byte("test content")
|
|
||||||
gitRepo.EXPECT().Create(ctx, "new-file.yaml", "main", data, "Create new file").Return(nil)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
err := repo.Create(ctx, "new-file.yaml", "main", data, "Create new file")
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates update to nanogit repo", func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
data := []byte("updated content")
|
|
||||||
gitRepo.EXPECT().Update(ctx, "existing-file.yaml", "main", data, "Update file").Return(nil)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
err := repo.Update(ctx, "existing-file.yaml", "main", data, "Update file")
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates write to nanogit repo", func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
data := []byte("file content")
|
|
||||||
gitRepo.EXPECT().Write(ctx, "file.yaml", "main", data, "Write file").Return(nil)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
err := repo.Write(ctx, "file.yaml", "main", data, "Write file")
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates delete to nanogit repo", func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
gitRepo.EXPECT().Delete(ctx, "file.yaml", "main", "Delete file").Return(nil)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
err := repo.Delete(ctx, "file.yaml", "main", "Delete file")
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates history to api repo", func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
expectedHistory := []provisioning.HistoryItem{
|
|
||||||
{
|
|
||||||
Ref: "commit1",
|
|
||||||
Message: "First commit",
|
|
||||||
Authors: []provisioning.Author{{Name: "Test User"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
apiRepo.EXPECT().History(ctx, "file.yaml", "main").Return(expectedHistory, nil)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
result, err := repo.History(ctx, "file.yaml", "main")
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedHistory, result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates latest ref to nanogit repo", func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
expectedRef := "abc123def456"
|
|
||||||
gitRepo.EXPECT().LatestRef(ctx).Return(expectedRef, nil)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
result, err := repo.LatestRef(ctx)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedRef, result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates compare files to nanogit repo", func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
expectedChanges := []repository.VersionedFileChange{
|
|
||||||
{
|
|
||||||
Action: repository.FileActionCreated,
|
|
||||||
Path: "new-file.yaml",
|
|
||||||
Ref: "feature-branch",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
gitRepo.EXPECT().CompareFiles(ctx, "main", "feature-branch").Return(expectedChanges, nil)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
result, err := repo.CompareFiles(ctx, "main", "feature-branch")
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedChanges, result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates resource URLs to api repo", func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
fileInfo := &repository.FileInfo{
|
|
||||||
Path: "dashboard.json",
|
|
||||||
Ref: "main",
|
|
||||||
Hash: "hash123",
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedURLs := &provisioning.ResourceURLs{
|
|
||||||
SourceURL: "https://github.com/test/repo/blob/main/dashboard.json",
|
|
||||||
RepositoryURL: "https://github.com/test/repo",
|
|
||||||
NewPullRequestURL: "https://github.com/test/repo/compare/main...feature",
|
|
||||||
}
|
|
||||||
|
|
||||||
apiRepo.EXPECT().ResourceURLs(ctx, fileInfo).Return(expectedURLs, nil)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
result, err := repo.ResourceURLs(ctx, fileInfo)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedURLs, result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("delegates clone to nanogit repo", func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
mockClonedRepo := repository.NewMockClonedRepository(t)
|
|
||||||
|
|
||||||
opts := repository.CloneOptions{
|
|
||||||
CreateIfNotExists: true,
|
|
||||||
PushOnWrites: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
gitRepo.EXPECT().Clone(ctx, opts).Return(mockClonedRepo, nil)
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
result, err := repo.Clone(ctx, opts)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, mockClonedRepo, result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGithubRepositoryValidation(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
config *provisioning.Repository
|
|
||||||
expectedErrors int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "missing github config",
|
|
||||||
config: &provisioning.Repository{
|
|
||||||
Spec: provisioning.RepositorySpec{
|
|
||||||
Type: provisioning.GitHubRepositoryType,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedErrors: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing github url",
|
|
||||||
config: &provisioning.Repository{
|
|
||||||
Spec: provisioning.RepositorySpec{
|
|
||||||
Type: provisioning.GitHubRepositoryType,
|
|
||||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
||||||
Branch: "main",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedErrors: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid github url",
|
|
||||||
config: &provisioning.Repository{
|
|
||||||
Spec: provisioning.RepositorySpec{
|
|
||||||
Type: provisioning.GitHubRepositoryType,
|
|
||||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
||||||
URL: "invalid-url",
|
|
||||||
Branch: "main",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedErrors: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-github url",
|
|
||||||
config: &provisioning.Repository{
|
|
||||||
Spec: provisioning.RepositorySpec{
|
|
||||||
Type: provisioning.GitHubRepositoryType,
|
|
||||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
||||||
URL: "https://gitlab.com/test/repo",
|
|
||||||
Branch: "main",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedErrors: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid github config",
|
|
||||||
config: &provisioning.Repository{
|
|
||||||
Spec: provisioning.RepositorySpec{
|
|
||||||
Type: provisioning.GitHubRepositoryType,
|
|
||||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
||||||
URL: "https://github.com/test/repo",
|
|
||||||
Branch: "main",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedErrors: 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
apiRepo := repository.NewMockGithubRepository(t)
|
|
||||||
gitRepo := repository.NewMockGitRepository(t)
|
|
||||||
|
|
||||||
// Set up mock expectations
|
|
||||||
gitRepo.EXPECT().Config().Return(tt.config)
|
|
||||||
if tt.expectedErrors == 0 {
|
|
||||||
// If no validation errors expected, nanogit validation should be called
|
|
||||||
gitRepo.EXPECT().Validate().Return(field.ErrorList{})
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := NewGithubRepository(apiRepo, gitRepo)
|
|
||||||
|
|
||||||
result := repo.Validate()
|
|
||||||
require.Len(t, result, tt.expectedErrors)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@@ -67,47 +65,6 @@ type FileInfo struct {
|
|||||||
Modified *metav1.Time
|
Modified *metav1.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate mockery --name CloneFn --structname MockCloneFn --inpackage --filename clone_fn_mock.go --with-expecter
|
|
||||||
type CloneFn func(ctx context.Context, opts CloneOptions) (ClonedRepository, error)
|
|
||||||
|
|
||||||
type CloneOptions struct {
|
|
||||||
// If the branch does not exist, create it
|
|
||||||
CreateIfNotExists bool
|
|
||||||
|
|
||||||
// Push on every write
|
|
||||||
PushOnWrites bool
|
|
||||||
|
|
||||||
// Maximum allowed size for repository clone in bytes (0 means no limit)
|
|
||||||
MaxSize int64
|
|
||||||
|
|
||||||
// Maximum time allowed for clone operation in seconds (0 means no limit)
|
|
||||||
Timeout time.Duration
|
|
||||||
|
|
||||||
// Progress is the writer to report progress to
|
|
||||||
Progress io.Writer
|
|
||||||
|
|
||||||
// BeforeFn is called before the clone operation starts
|
|
||||||
BeforeFn func() error
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:generate mockery --name ClonableRepository --structname MockClonableRepository --inpackage --filename clonable_repository_mock.go --with-expecter
|
|
||||||
type ClonableRepository interface {
|
|
||||||
Clone(ctx context.Context, opts CloneOptions) (ClonedRepository, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PushOptions struct {
|
|
||||||
Timeout time.Duration
|
|
||||||
Progress io.Writer
|
|
||||||
BeforeFn func() error
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:generate mockery --name ClonedRepository --structname MockClonedRepository --inpackage --filename cloned_repository_mock.go --with-expecter
|
|
||||||
type ClonedRepository interface {
|
|
||||||
ReaderWriter
|
|
||||||
Push(ctx context.Context, opts PushOptions) error
|
|
||||||
Remove(ctx context.Context) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// An entry in the file tree, as returned by 'ReadFileTree'. Like FileInfo, but contains less information.
|
// An entry in the file tree, as returned by 'ReadFileTree'. Like FileInfo, but contains less information.
|
||||||
type FileTreeEntry struct {
|
type FileTreeEntry struct {
|
||||||
// The path to the file from the base path given (if any).
|
// The path to the file from the base path given (if any).
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockStageableRepository is an autogenerated mock type for the StageableRepository type
|
||||||
|
type MockStageableRepository struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockStageableRepository_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *MockStageableRepository) EXPECT() *MockStageableRepository_Expecter {
|
||||||
|
return &MockStageableRepository_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage provides a mock function with given fields: ctx, opts
|
||||||
|
func (_m *MockStageableRepository) Stage(ctx context.Context, opts StageOptions) (StagedRepository, error) {
|
||||||
|
ret := _m.Called(ctx, opts)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Stage")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 StagedRepository
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, StageOptions) (StagedRepository, error)); ok {
|
||||||
|
return rf(ctx, opts)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, StageOptions) StagedRepository); ok {
|
||||||
|
r0 = rf(ctx, opts)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(StagedRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, StageOptions) error); ok {
|
||||||
|
r1 = rf(ctx, opts)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockStageableRepository_Stage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stage'
|
||||||
|
type MockStageableRepository_Stage_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - opts StageOptions
|
||||||
|
func (_e *MockStageableRepository_Expecter) Stage(ctx interface{}, opts interface{}) *MockStageableRepository_Stage_Call {
|
||||||
|
return &MockStageableRepository_Stage_Call{Call: _e.mock.On("Stage", ctx, opts)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockStageableRepository_Stage_Call) Run(run func(ctx context.Context, opts StageOptions)) *MockStageableRepository_Stage_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(StageOptions))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockStageableRepository_Stage_Call) Return(_a0 StagedRepository, _a1 error) *MockStageableRepository_Stage_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockStageableRepository_Stage_Call) RunAndReturn(run func(context.Context, StageOptions) (StagedRepository, error)) *MockStageableRepository_Stage_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockStageableRepository creates a new instance of MockStageableRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
// The first argument is typically a *testing.T value.
|
||||||
|
func NewMockStageableRepository(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *MockStageableRepository {
|
||||||
|
mock := &MockStageableRepository{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-app-sdk/logging"
|
||||||
|
"github.com/grafana/nanogit"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StageOptions struct {
|
||||||
|
// Push on every write
|
||||||
|
PushOnWrites bool
|
||||||
|
// Maximum time allowed for clone operation in seconds (0 means no limit)
|
||||||
|
Timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate mockery --name StageableRepository --structname MockStageableRepository --inpackage --filename stageable_repository_mock.go --with-expecter
|
||||||
|
type StageableRepository interface {
|
||||||
|
Stage(ctx context.Context, opts StageOptions) (StagedRepository, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate mockery --name StagedRepository --structname MockStagedRepository --inpackage --filename staged_repository_mock.go --with-expecter
|
||||||
|
type StagedRepository interface {
|
||||||
|
ReaderWriter
|
||||||
|
Push(ctx context.Context) error
|
||||||
|
Remove(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapWithStageAndPushIfPossible attempts to stage the given repository. If staging is supported,
|
||||||
|
// it runs the provided function on the staged repository, then pushes any changes and cleans up the staged repository.
|
||||||
|
// If staging is not supported, it runs the function on the original repository without pushing.
|
||||||
|
// The 'staged' argument to the function indicates whether a staged repository was used.
|
||||||
|
func WrapWithStageAndPushIfPossible(
|
||||||
|
ctx context.Context,
|
||||||
|
repo Repository,
|
||||||
|
stageOptions StageOptions,
|
||||||
|
fn func(repo Repository, staged bool) error,
|
||||||
|
) error {
|
||||||
|
stageable, ok := repo.(StageableRepository)
|
||||||
|
if !ok {
|
||||||
|
return fn(repo, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
staged, err := stageable.Stage(ctx, stageOptions)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stage repository: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't, we simply log it
|
||||||
|
// FIXME: should we handle this differently?
|
||||||
|
defer func() {
|
||||||
|
if err := staged.Remove(ctx); err != nil {
|
||||||
|
logging.FromContext(ctx).Error("failed to remove staged repository after export", "err", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := fn(staged, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = staged.Push(ctx); err != nil {
|
||||||
|
if errors.Is(err, nanogit.ErrNothingToPush) {
|
||||||
|
return nil // OK, already pushed
|
||||||
|
}
|
||||||
|
return fmt.Errorf("wrapped push error: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+103
-104
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
@@ -11,21 +11,21 @@ import (
|
|||||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockClonedRepository is an autogenerated mock type for the ClonedRepository type
|
// MockStagedRepository is an autogenerated mock type for the StagedRepository type
|
||||||
type MockClonedRepository struct {
|
type MockStagedRepository struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockClonedRepository_Expecter struct {
|
type MockStagedRepository_Expecter struct {
|
||||||
mock *mock.Mock
|
mock *mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockClonedRepository) EXPECT() *MockClonedRepository_Expecter {
|
func (_m *MockStagedRepository) EXPECT() *MockStagedRepository_Expecter {
|
||||||
return &MockClonedRepository_Expecter{mock: &_m.Mock}
|
return &MockStagedRepository_Expecter{mock: &_m.Mock}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config provides a mock function with no fields
|
// Config provides a mock function with no fields
|
||||||
func (_m *MockClonedRepository) Config() *v0alpha1.Repository {
|
func (_m *MockStagedRepository) Config() *v0alpha1.Repository {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
@@ -44,35 +44,35 @@ func (_m *MockClonedRepository) Config() *v0alpha1.Repository {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockClonedRepository_Config_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Config'
|
// MockStagedRepository_Config_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Config'
|
||||||
type MockClonedRepository_Config_Call struct {
|
type MockStagedRepository_Config_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is a helper method to define mock.On call
|
// Config is a helper method to define mock.On call
|
||||||
func (_e *MockClonedRepository_Expecter) Config() *MockClonedRepository_Config_Call {
|
func (_e *MockStagedRepository_Expecter) Config() *MockStagedRepository_Config_Call {
|
||||||
return &MockClonedRepository_Config_Call{Call: _e.mock.On("Config")}
|
return &MockStagedRepository_Config_Call{Call: _e.mock.On("Config")}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Config_Call) Run(run func()) *MockClonedRepository_Config_Call {
|
func (_c *MockStagedRepository_Config_Call) Run(run func()) *MockStagedRepository_Config_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run()
|
run()
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Config_Call) Return(_a0 *v0alpha1.Repository) *MockClonedRepository_Config_Call {
|
func (_c *MockStagedRepository_Config_Call) Return(_a0 *v0alpha1.Repository) *MockStagedRepository_Config_Call {
|
||||||
_c.Call.Return(_a0)
|
_c.Call.Return(_a0)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Config_Call) RunAndReturn(run func() *v0alpha1.Repository) *MockClonedRepository_Config_Call {
|
func (_c *MockStagedRepository_Config_Call) RunAndReturn(run func() *v0alpha1.Repository) *MockStagedRepository_Config_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create provides a mock function with given fields: ctx, path, ref, data, message
|
// Create provides a mock function with given fields: ctx, path, ref, data, message
|
||||||
func (_m *MockClonedRepository) Create(ctx context.Context, path string, ref string, data []byte, message string) error {
|
func (_m *MockStagedRepository) Create(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||||
ret := _m.Called(ctx, path, ref, data, message)
|
ret := _m.Called(ctx, path, ref, data, message)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
@@ -89,8 +89,8 @@ func (_m *MockClonedRepository) Create(ctx context.Context, path string, ref str
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockClonedRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
|
// MockStagedRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
|
||||||
type MockClonedRepository_Create_Call struct {
|
type MockStagedRepository_Create_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,29 +100,29 @@ type MockClonedRepository_Create_Call struct {
|
|||||||
// - ref string
|
// - ref string
|
||||||
// - data []byte
|
// - data []byte
|
||||||
// - message string
|
// - message string
|
||||||
func (_e *MockClonedRepository_Expecter) Create(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockClonedRepository_Create_Call {
|
func (_e *MockStagedRepository_Expecter) Create(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockStagedRepository_Create_Call {
|
||||||
return &MockClonedRepository_Create_Call{Call: _e.mock.On("Create", ctx, path, ref, data, message)}
|
return &MockStagedRepository_Create_Call{Call: _e.mock.On("Create", ctx, path, ref, data, message)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Create_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockClonedRepository_Create_Call {
|
func (_c *MockStagedRepository_Create_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockStagedRepository_Create_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string))
|
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Create_Call) Return(_a0 error) *MockClonedRepository_Create_Call {
|
func (_c *MockStagedRepository_Create_Call) Return(_a0 error) *MockStagedRepository_Create_Call {
|
||||||
_c.Call.Return(_a0)
|
_c.Call.Return(_a0)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Create_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockClonedRepository_Create_Call {
|
func (_c *MockStagedRepository_Create_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockStagedRepository_Create_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete provides a mock function with given fields: ctx, path, ref, message
|
// Delete provides a mock function with given fields: ctx, path, ref, message
|
||||||
func (_m *MockClonedRepository) Delete(ctx context.Context, path string, ref string, message string) error {
|
func (_m *MockStagedRepository) Delete(ctx context.Context, path string, ref string, message string) error {
|
||||||
ret := _m.Called(ctx, path, ref, message)
|
ret := _m.Called(ctx, path, ref, message)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
@@ -139,8 +139,8 @@ func (_m *MockClonedRepository) Delete(ctx context.Context, path string, ref str
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockClonedRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
|
// MockStagedRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
|
||||||
type MockClonedRepository_Delete_Call struct {
|
type MockStagedRepository_Delete_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,38 +149,38 @@ type MockClonedRepository_Delete_Call struct {
|
|||||||
// - path string
|
// - path string
|
||||||
// - ref string
|
// - ref string
|
||||||
// - message string
|
// - message string
|
||||||
func (_e *MockClonedRepository_Expecter) Delete(ctx interface{}, path interface{}, ref interface{}, message interface{}) *MockClonedRepository_Delete_Call {
|
func (_e *MockStagedRepository_Expecter) Delete(ctx interface{}, path interface{}, ref interface{}, message interface{}) *MockStagedRepository_Delete_Call {
|
||||||
return &MockClonedRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, path, ref, message)}
|
return &MockStagedRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, path, ref, message)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Delete_Call) Run(run func(ctx context.Context, path string, ref string, message string)) *MockClonedRepository_Delete_Call {
|
func (_c *MockStagedRepository_Delete_Call) Run(run func(ctx context.Context, path string, ref string, message string)) *MockStagedRepository_Delete_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string))
|
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Delete_Call) Return(_a0 error) *MockClonedRepository_Delete_Call {
|
func (_c *MockStagedRepository_Delete_Call) Return(_a0 error) *MockStagedRepository_Delete_Call {
|
||||||
_c.Call.Return(_a0)
|
_c.Call.Return(_a0)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Delete_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockClonedRepository_Delete_Call {
|
func (_c *MockStagedRepository_Delete_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockStagedRepository_Delete_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push provides a mock function with given fields: ctx, opts
|
// Push provides a mock function with given fields: ctx
|
||||||
func (_m *MockClonedRepository) Push(ctx context.Context, opts PushOptions) error {
|
func (_m *MockStagedRepository) Push(ctx context.Context) error {
|
||||||
ret := _m.Called(ctx, opts)
|
ret := _m.Called(ctx)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for Push")
|
panic("no return value specified for Push")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, PushOptions) error); ok {
|
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||||
r0 = rf(ctx, opts)
|
r0 = rf(ctx)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
@@ -188,37 +188,36 @@ func (_m *MockClonedRepository) Push(ctx context.Context, opts PushOptions) erro
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockClonedRepository_Push_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Push'
|
// MockStagedRepository_Push_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Push'
|
||||||
type MockClonedRepository_Push_Call struct {
|
type MockStagedRepository_Push_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push is a helper method to define mock.On call
|
// Push is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
// - opts PushOptions
|
func (_e *MockStagedRepository_Expecter) Push(ctx interface{}) *MockStagedRepository_Push_Call {
|
||||||
func (_e *MockClonedRepository_Expecter) Push(ctx interface{}, opts interface{}) *MockClonedRepository_Push_Call {
|
return &MockStagedRepository_Push_Call{Call: _e.mock.On("Push", ctx)}
|
||||||
return &MockClonedRepository_Push_Call{Call: _e.mock.On("Push", ctx, opts)}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Push_Call) Run(run func(ctx context.Context, opts PushOptions)) *MockClonedRepository_Push_Call {
|
func (_c *MockStagedRepository_Push_Call) Run(run func(ctx context.Context)) *MockStagedRepository_Push_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context), args[1].(PushOptions))
|
run(args[0].(context.Context))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Push_Call) Return(_a0 error) *MockClonedRepository_Push_Call {
|
func (_c *MockStagedRepository_Push_Call) Return(_a0 error) *MockStagedRepository_Push_Call {
|
||||||
_c.Call.Return(_a0)
|
_c.Call.Return(_a0)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Push_Call) RunAndReturn(run func(context.Context, PushOptions) error) *MockClonedRepository_Push_Call {
|
func (_c *MockStagedRepository_Push_Call) RunAndReturn(run func(context.Context) error) *MockStagedRepository_Push_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read provides a mock function with given fields: ctx, path, ref
|
// Read provides a mock function with given fields: ctx, path, ref
|
||||||
func (_m *MockClonedRepository) Read(ctx context.Context, path string, ref string) (*FileInfo, error) {
|
func (_m *MockStagedRepository) Read(ctx context.Context, path string, ref string) (*FileInfo, error) {
|
||||||
ret := _m.Called(ctx, path, ref)
|
ret := _m.Called(ctx, path, ref)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
@@ -247,8 +246,8 @@ func (_m *MockClonedRepository) Read(ctx context.Context, path string, ref strin
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockClonedRepository_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read'
|
// MockStagedRepository_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read'
|
||||||
type MockClonedRepository_Read_Call struct {
|
type MockStagedRepository_Read_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,29 +255,29 @@ type MockClonedRepository_Read_Call struct {
|
|||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
// - path string
|
// - path string
|
||||||
// - ref string
|
// - ref string
|
||||||
func (_e *MockClonedRepository_Expecter) Read(ctx interface{}, path interface{}, ref interface{}) *MockClonedRepository_Read_Call {
|
func (_e *MockStagedRepository_Expecter) Read(ctx interface{}, path interface{}, ref interface{}) *MockStagedRepository_Read_Call {
|
||||||
return &MockClonedRepository_Read_Call{Call: _e.mock.On("Read", ctx, path, ref)}
|
return &MockStagedRepository_Read_Call{Call: _e.mock.On("Read", ctx, path, ref)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Read_Call) Run(run func(ctx context.Context, path string, ref string)) *MockClonedRepository_Read_Call {
|
func (_c *MockStagedRepository_Read_Call) Run(run func(ctx context.Context, path string, ref string)) *MockStagedRepository_Read_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Read_Call) Return(_a0 *FileInfo, _a1 error) *MockClonedRepository_Read_Call {
|
func (_c *MockStagedRepository_Read_Call) Return(_a0 *FileInfo, _a1 error) *MockStagedRepository_Read_Call {
|
||||||
_c.Call.Return(_a0, _a1)
|
_c.Call.Return(_a0, _a1)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*FileInfo, error)) *MockClonedRepository_Read_Call {
|
func (_c *MockStagedRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*FileInfo, error)) *MockStagedRepository_Read_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadTree provides a mock function with given fields: ctx, ref
|
// ReadTree provides a mock function with given fields: ctx, ref
|
||||||
func (_m *MockClonedRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) {
|
func (_m *MockStagedRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) {
|
||||||
ret := _m.Called(ctx, ref)
|
ret := _m.Called(ctx, ref)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
@@ -307,37 +306,37 @@ func (_m *MockClonedRepository) ReadTree(ctx context.Context, ref string) ([]Fil
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockClonedRepository_ReadTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadTree'
|
// MockStagedRepository_ReadTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadTree'
|
||||||
type MockClonedRepository_ReadTree_Call struct {
|
type MockStagedRepository_ReadTree_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadTree is a helper method to define mock.On call
|
// ReadTree is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
// - ref string
|
// - ref string
|
||||||
func (_e *MockClonedRepository_Expecter) ReadTree(ctx interface{}, ref interface{}) *MockClonedRepository_ReadTree_Call {
|
func (_e *MockStagedRepository_Expecter) ReadTree(ctx interface{}, ref interface{}) *MockStagedRepository_ReadTree_Call {
|
||||||
return &MockClonedRepository_ReadTree_Call{Call: _e.mock.On("ReadTree", ctx, ref)}
|
return &MockStagedRepository_ReadTree_Call{Call: _e.mock.On("ReadTree", ctx, ref)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_ReadTree_Call) Run(run func(ctx context.Context, ref string)) *MockClonedRepository_ReadTree_Call {
|
func (_c *MockStagedRepository_ReadTree_Call) Run(run func(ctx context.Context, ref string)) *MockStagedRepository_ReadTree_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context), args[1].(string))
|
run(args[0].(context.Context), args[1].(string))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_ReadTree_Call) Return(_a0 []FileTreeEntry, _a1 error) *MockClonedRepository_ReadTree_Call {
|
func (_c *MockStagedRepository_ReadTree_Call) Return(_a0 []FileTreeEntry, _a1 error) *MockStagedRepository_ReadTree_Call {
|
||||||
_c.Call.Return(_a0, _a1)
|
_c.Call.Return(_a0, _a1)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]FileTreeEntry, error)) *MockClonedRepository_ReadTree_Call {
|
func (_c *MockStagedRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]FileTreeEntry, error)) *MockStagedRepository_ReadTree_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove provides a mock function with given fields: ctx
|
// Remove provides a mock function with given fields: ctx
|
||||||
func (_m *MockClonedRepository) Remove(ctx context.Context) error {
|
func (_m *MockStagedRepository) Remove(ctx context.Context) error {
|
||||||
ret := _m.Called(ctx)
|
ret := _m.Called(ctx)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
@@ -354,36 +353,36 @@ func (_m *MockClonedRepository) Remove(ctx context.Context) error {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockClonedRepository_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove'
|
// MockStagedRepository_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove'
|
||||||
type MockClonedRepository_Remove_Call struct {
|
type MockStagedRepository_Remove_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove is a helper method to define mock.On call
|
// Remove is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
func (_e *MockClonedRepository_Expecter) Remove(ctx interface{}) *MockClonedRepository_Remove_Call {
|
func (_e *MockStagedRepository_Expecter) Remove(ctx interface{}) *MockStagedRepository_Remove_Call {
|
||||||
return &MockClonedRepository_Remove_Call{Call: _e.mock.On("Remove", ctx)}
|
return &MockStagedRepository_Remove_Call{Call: _e.mock.On("Remove", ctx)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Remove_Call) Run(run func(ctx context.Context)) *MockClonedRepository_Remove_Call {
|
func (_c *MockStagedRepository_Remove_Call) Run(run func(ctx context.Context)) *MockStagedRepository_Remove_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context))
|
run(args[0].(context.Context))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Remove_Call) Return(_a0 error) *MockClonedRepository_Remove_Call {
|
func (_c *MockStagedRepository_Remove_Call) Return(_a0 error) *MockStagedRepository_Remove_Call {
|
||||||
_c.Call.Return(_a0)
|
_c.Call.Return(_a0)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Remove_Call) RunAndReturn(run func(context.Context) error) *MockClonedRepository_Remove_Call {
|
func (_c *MockStagedRepository_Remove_Call) RunAndReturn(run func(context.Context) error) *MockStagedRepository_Remove_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test provides a mock function with given fields: ctx
|
// Test provides a mock function with given fields: ctx
|
||||||
func (_m *MockClonedRepository) Test(ctx context.Context) (*v0alpha1.TestResults, error) {
|
func (_m *MockStagedRepository) Test(ctx context.Context) (*v0alpha1.TestResults, error) {
|
||||||
ret := _m.Called(ctx)
|
ret := _m.Called(ctx)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
@@ -412,36 +411,36 @@ func (_m *MockClonedRepository) Test(ctx context.Context) (*v0alpha1.TestResults
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockClonedRepository_Test_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Test'
|
// MockStagedRepository_Test_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Test'
|
||||||
type MockClonedRepository_Test_Call struct {
|
type MockStagedRepository_Test_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test is a helper method to define mock.On call
|
// Test is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
func (_e *MockClonedRepository_Expecter) Test(ctx interface{}) *MockClonedRepository_Test_Call {
|
func (_e *MockStagedRepository_Expecter) Test(ctx interface{}) *MockStagedRepository_Test_Call {
|
||||||
return &MockClonedRepository_Test_Call{Call: _e.mock.On("Test", ctx)}
|
return &MockStagedRepository_Test_Call{Call: _e.mock.On("Test", ctx)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Test_Call) Run(run func(ctx context.Context)) *MockClonedRepository_Test_Call {
|
func (_c *MockStagedRepository_Test_Call) Run(run func(ctx context.Context)) *MockStagedRepository_Test_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context))
|
run(args[0].(context.Context))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Test_Call) Return(_a0 *v0alpha1.TestResults, _a1 error) *MockClonedRepository_Test_Call {
|
func (_c *MockStagedRepository_Test_Call) Return(_a0 *v0alpha1.TestResults, _a1 error) *MockStagedRepository_Test_Call {
|
||||||
_c.Call.Return(_a0, _a1)
|
_c.Call.Return(_a0, _a1)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Test_Call) RunAndReturn(run func(context.Context) (*v0alpha1.TestResults, error)) *MockClonedRepository_Test_Call {
|
func (_c *MockStagedRepository_Test_Call) RunAndReturn(run func(context.Context) (*v0alpha1.TestResults, error)) *MockStagedRepository_Test_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update provides a mock function with given fields: ctx, path, ref, data, message
|
// Update provides a mock function with given fields: ctx, path, ref, data, message
|
||||||
func (_m *MockClonedRepository) Update(ctx context.Context, path string, ref string, data []byte, message string) error {
|
func (_m *MockStagedRepository) Update(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||||
ret := _m.Called(ctx, path, ref, data, message)
|
ret := _m.Called(ctx, path, ref, data, message)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
@@ -458,8 +457,8 @@ func (_m *MockClonedRepository) Update(ctx context.Context, path string, ref str
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockClonedRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
|
// MockStagedRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
|
||||||
type MockClonedRepository_Update_Call struct {
|
type MockStagedRepository_Update_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,29 +468,29 @@ type MockClonedRepository_Update_Call struct {
|
|||||||
// - ref string
|
// - ref string
|
||||||
// - data []byte
|
// - data []byte
|
||||||
// - message string
|
// - message string
|
||||||
func (_e *MockClonedRepository_Expecter) Update(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockClonedRepository_Update_Call {
|
func (_e *MockStagedRepository_Expecter) Update(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockStagedRepository_Update_Call {
|
||||||
return &MockClonedRepository_Update_Call{Call: _e.mock.On("Update", ctx, path, ref, data, message)}
|
return &MockStagedRepository_Update_Call{Call: _e.mock.On("Update", ctx, path, ref, data, message)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Update_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockClonedRepository_Update_Call {
|
func (_c *MockStagedRepository_Update_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockStagedRepository_Update_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string))
|
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Update_Call) Return(_a0 error) *MockClonedRepository_Update_Call {
|
func (_c *MockStagedRepository_Update_Call) Return(_a0 error) *MockStagedRepository_Update_Call {
|
||||||
_c.Call.Return(_a0)
|
_c.Call.Return(_a0)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Update_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockClonedRepository_Update_Call {
|
func (_c *MockStagedRepository_Update_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockStagedRepository_Update_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate provides a mock function with no fields
|
// Validate provides a mock function with no fields
|
||||||
func (_m *MockClonedRepository) Validate() field.ErrorList {
|
func (_m *MockStagedRepository) Validate() field.ErrorList {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
@@ -510,35 +509,35 @@ func (_m *MockClonedRepository) Validate() field.ErrorList {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockClonedRepository_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
|
// MockStagedRepository_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
|
||||||
type MockClonedRepository_Validate_Call struct {
|
type MockStagedRepository_Validate_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate is a helper method to define mock.On call
|
// Validate is a helper method to define mock.On call
|
||||||
func (_e *MockClonedRepository_Expecter) Validate() *MockClonedRepository_Validate_Call {
|
func (_e *MockStagedRepository_Expecter) Validate() *MockStagedRepository_Validate_Call {
|
||||||
return &MockClonedRepository_Validate_Call{Call: _e.mock.On("Validate")}
|
return &MockStagedRepository_Validate_Call{Call: _e.mock.On("Validate")}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Validate_Call) Run(run func()) *MockClonedRepository_Validate_Call {
|
func (_c *MockStagedRepository_Validate_Call) Run(run func()) *MockStagedRepository_Validate_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run()
|
run()
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Validate_Call) Return(_a0 field.ErrorList) *MockClonedRepository_Validate_Call {
|
func (_c *MockStagedRepository_Validate_Call) Return(_a0 field.ErrorList) *MockStagedRepository_Validate_Call {
|
||||||
_c.Call.Return(_a0)
|
_c.Call.Return(_a0)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Validate_Call) RunAndReturn(run func() field.ErrorList) *MockClonedRepository_Validate_Call {
|
func (_c *MockStagedRepository_Validate_Call) RunAndReturn(run func() field.ErrorList) *MockStagedRepository_Validate_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write provides a mock function with given fields: ctx, path, ref, data, message
|
// Write provides a mock function with given fields: ctx, path, ref, data, message
|
||||||
func (_m *MockClonedRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
|
func (_m *MockStagedRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
|
||||||
ret := _m.Called(ctx, path, ref, data, message)
|
ret := _m.Called(ctx, path, ref, data, message)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
@@ -555,8 +554,8 @@ func (_m *MockClonedRepository) Write(ctx context.Context, path string, ref stri
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockClonedRepository_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write'
|
// MockStagedRepository_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write'
|
||||||
type MockClonedRepository_Write_Call struct {
|
type MockStagedRepository_Write_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,34 +565,34 @@ type MockClonedRepository_Write_Call struct {
|
|||||||
// - ref string
|
// - ref string
|
||||||
// - data []byte
|
// - data []byte
|
||||||
// - message string
|
// - message string
|
||||||
func (_e *MockClonedRepository_Expecter) Write(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockClonedRepository_Write_Call {
|
func (_e *MockStagedRepository_Expecter) Write(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockStagedRepository_Write_Call {
|
||||||
return &MockClonedRepository_Write_Call{Call: _e.mock.On("Write", ctx, path, ref, data, message)}
|
return &MockStagedRepository_Write_Call{Call: _e.mock.On("Write", ctx, path, ref, data, message)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Write_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockClonedRepository_Write_Call {
|
func (_c *MockStagedRepository_Write_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockStagedRepository_Write_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string))
|
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Write_Call) Return(_a0 error) *MockClonedRepository_Write_Call {
|
func (_c *MockStagedRepository_Write_Call) Return(_a0 error) *MockStagedRepository_Write_Call {
|
||||||
_c.Call.Return(_a0)
|
_c.Call.Return(_a0)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *MockClonedRepository_Write_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockClonedRepository_Write_Call {
|
func (_c *MockStagedRepository_Write_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockStagedRepository_Write_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockClonedRepository creates a new instance of MockClonedRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
// NewMockStagedRepository creates a new instance of MockStagedRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
// The first argument is typically a *testing.T value.
|
// The first argument is typically a *testing.T value.
|
||||||
func NewMockClonedRepository(t interface {
|
func NewMockStagedRepository(t interface {
|
||||||
mock.TestingT
|
mock.TestingT
|
||||||
Cleanup(func())
|
Cleanup(func())
|
||||||
}) *MockClonedRepository {
|
}) *MockStagedRepository {
|
||||||
mock := &MockClonedRepository{}
|
mock := &MockStagedRepository{}
|
||||||
mock.Mock.Test(t)
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockStagedRepo struct {
|
||||||
|
*MockStageableRepository
|
||||||
|
*MockStagedRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_WrapWithStageAndPushIfPossible_NonStageableRepository(t *testing.T) {
|
||||||
|
nonStageable := NewMockRepository(t)
|
||||||
|
var called bool
|
||||||
|
fn := func(repo Repository, staged bool) error {
|
||||||
|
called = true
|
||||||
|
return errors.New("operation failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := WrapWithStageAndPushIfPossible(context.Background(), nonStageable, StageOptions{}, fn)
|
||||||
|
require.EqualError(t, err, "operation failed")
|
||||||
|
require.True(t, called)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrapWithStageAndPushIfPossible(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setupMocks func(t *testing.T) *mockStagedRepo
|
||||||
|
operation func(repo Repository, staged bool) error
|
||||||
|
expectedError string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "successful stage, operation, and push",
|
||||||
|
setupMocks: func(t *testing.T) *mockStagedRepo {
|
||||||
|
mockRepo := NewMockStageableRepository(t)
|
||||||
|
mockStaged := NewMockStagedRepository(t)
|
||||||
|
|
||||||
|
mockRepo.EXPECT().Stage(mock.Anything, StageOptions{}).Return(mockStaged, nil)
|
||||||
|
mockStaged.EXPECT().Push(mock.Anything).Return(nil)
|
||||||
|
mockStaged.EXPECT().Remove(mock.Anything).Return(nil)
|
||||||
|
return &mockStagedRepo{
|
||||||
|
MockStageableRepository: mockRepo,
|
||||||
|
MockStagedRepository: mockStaged,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
operation: func(repo Repository, staged bool) error {
|
||||||
|
require.True(t, staged)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stage failure",
|
||||||
|
setupMocks: func(t *testing.T) *mockStagedRepo {
|
||||||
|
mockRepo := NewMockStageableRepository(t)
|
||||||
|
|
||||||
|
mockRepo.EXPECT().Stage(mock.Anything, StageOptions{}).Return(nil, errors.New("stage failed"))
|
||||||
|
|
||||||
|
return &mockStagedRepo{
|
||||||
|
MockStageableRepository: mockRepo,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
operation: func(repo Repository, staged bool) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
expectedError: "stage repository: stage failed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "operation failure",
|
||||||
|
setupMocks: func(t *testing.T) *mockStagedRepo {
|
||||||
|
mockRepo := NewMockStageableRepository(t)
|
||||||
|
mockStaged := NewMockStagedRepository(t)
|
||||||
|
|
||||||
|
mockRepo.EXPECT().Stage(mock.Anything, StageOptions{}).Return(mockStaged, nil)
|
||||||
|
mockStaged.EXPECT().Remove(mock.Anything).Return(nil)
|
||||||
|
|
||||||
|
return &mockStagedRepo{
|
||||||
|
MockStageableRepository: mockRepo,
|
||||||
|
MockStagedRepository: mockStaged,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
operation: func(repo Repository, staged bool) error {
|
||||||
|
return errors.New("operation failed")
|
||||||
|
},
|
||||||
|
expectedError: "operation failed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "push failure",
|
||||||
|
setupMocks: func(t *testing.T) *mockStagedRepo {
|
||||||
|
mockRepo := NewMockStageableRepository(t)
|
||||||
|
mockStaged := NewMockStagedRepository(t)
|
||||||
|
|
||||||
|
mockRepo.EXPECT().Stage(mock.Anything, StageOptions{}).Return(mockStaged, nil)
|
||||||
|
mockStaged.EXPECT().Push(mock.Anything).Return(errors.New("push failed"))
|
||||||
|
mockStaged.EXPECT().Remove(mock.Anything).Return(nil)
|
||||||
|
|
||||||
|
return &mockStagedRepo{
|
||||||
|
MockStageableRepository: mockRepo,
|
||||||
|
MockStagedRepository: mockStaged,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
operation: func(repo Repository, staged bool) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
expectedError: "wrapped push error: push failed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remove failure should only log",
|
||||||
|
setupMocks: func(t *testing.T) *mockStagedRepo {
|
||||||
|
mockRepo := NewMockStageableRepository(t)
|
||||||
|
mockStaged := NewMockStagedRepository(t)
|
||||||
|
|
||||||
|
mockRepo.EXPECT().Stage(mock.Anything, StageOptions{}).Return(mockStaged, nil)
|
||||||
|
mockStaged.EXPECT().Push(mock.Anything).Return(nil)
|
||||||
|
mockStaged.EXPECT().Remove(mock.Anything).Return(errors.New("remove failed"))
|
||||||
|
|
||||||
|
return &mockStagedRepo{
|
||||||
|
MockStageableRepository: mockRepo,
|
||||||
|
MockStagedRepository: mockStaged,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
operation: func(repo Repository, staged bool) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
repo := tt.setupMocks(t)
|
||||||
|
err := WrapWithStageAndPushIfPossible(context.Background(), repo, StageOptions{}, tt.operation)
|
||||||
|
|
||||||
|
if tt.expectedError != "" {
|
||||||
|
require.EqualError(t, err, tt.expectedError)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -96,7 +96,7 @@ func ValidateRepository(repo Repository) field.ErrorList {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromFieldError(err *field.Error) *provisioning.TestResults {
|
func FromFieldError(err *field.Error) *provisioning.TestResults {
|
||||||
return &provisioning.TestResults{
|
return &provisioning.TestResults{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
Success: false,
|
Success: false,
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ func TestFromFieldError(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := fromFieldError(tt.fieldError)
|
result := FromFieldError(tt.fieldError)
|
||||||
|
|
||||||
require.NotNil(t, result)
|
require.NotNil(t, result)
|
||||||
require.Equal(t, tt.expectedCode, result.Code)
|
require.Equal(t, tt.expectedCode, result.Code)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||||
|
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
authlib "github.com/grafana/authlib/types"
|
authlib "github.com/grafana/authlib/types"
|
||||||
"github.com/grafana/grafana-app-sdk/logging"
|
"github.com/grafana/grafana-app-sdk/logging"
|
||||||
@@ -76,9 +77,8 @@ func (r *DualReadWriter) Delete(ctx context.Context, opts DualWriteOptions) (*Pa
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement this
|
|
||||||
if safepath.IsDir(opts.Path) {
|
if safepath.IsDir(opts.Path) {
|
||||||
return nil, fmt.Errorf("folder delete not supported")
|
return r.deleteFolder(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the file from the default branch as it won't exist in the possibly new branch
|
// Read the file from the default branch as it won't exist in the possibly new branch
|
||||||
@@ -166,6 +166,12 @@ func (r *DualReadWriter) CreateFolder(ctx context.Context, opts DualWriteOptions
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
urls, err := getFolderURLs(ctx, opts.Path, opts.Ref, r.repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
wrap.URLs = urls
|
||||||
|
|
||||||
if opts.Ref == "" {
|
if opts.Ref == "" {
|
||||||
folderName, err := r.folders.EnsureFolderPathExist(ctx, opts.Path)
|
folderName, err := r.folders.EnsureFolderPathExist(ctx, opts.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -317,3 +323,141 @@ func (r *DualReadWriter) authorizeCreateFolder(ctx context.Context, _ string) er
|
|||||||
return apierrors.NewForbidden(FolderResource.GroupResource(), "",
|
return apierrors.NewForbidden(FolderResource.GroupResource(), "",
|
||||||
fmt.Errorf("must be admin or editor to access folders with provisioning"))
|
fmt.Errorf("must be admin or editor to access folders with provisioning"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *DualReadWriter) deleteFolder(ctx context.Context, opts DualWriteOptions) (*ParsedResource, error) {
|
||||||
|
// if the ref is set, it is not the active branch, so just delete the files from the branch
|
||||||
|
// and do not delete the items from grafana itself
|
||||||
|
if opts.Ref != "" {
|
||||||
|
err := r.repo.Delete(ctx, opts.Path, opts.Ref, opts.Message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error deleting folder from repository: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return folderDeleteResponse(ctx, opts.Path, opts.Ref, r.repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// before deleting from the repo, first get all children resources to delete from grafana afterwards
|
||||||
|
treeEntries, err := r.repo.ReadTree(ctx, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read repository tree: %w", err)
|
||||||
|
}
|
||||||
|
// note: parsedFolders will include the folder itself
|
||||||
|
parsedResources, parsedFolders, err := r.getChildren(ctx, opts.Path, treeEntries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse resources in folder: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete from the repo
|
||||||
|
err = r.repo.Delete(ctx, opts.Path, opts.Ref, opts.Message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("delete folder from repository: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete from grafana
|
||||||
|
ctx, _, err = identity.WithProvisioningIdentity(ctx, r.repo.Config().Namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := r.deleteChildren(ctx, parsedResources, parsedFolders); err != nil {
|
||||||
|
return nil, fmt.Errorf("delete folder from grafana: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return folderDeleteResponse(ctx, opts.Path, opts.Ref, r.repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFolderURLs(ctx context.Context, path, ref string, repo repository.Repository) (*provisioning.ResourceURLs, error) {
|
||||||
|
if urlRepo, ok := repo.(repository.RepositoryWithURLs); ok && ref != "" {
|
||||||
|
urls, err := urlRepo.ResourceURLs(ctx, &repository.FileInfo{Path: path, Ref: ref})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return urls, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func folderDeleteResponse(ctx context.Context, path, ref string, repo repository.Repository) (*ParsedResource, error) {
|
||||||
|
urls, err := getFolderURLs(ctx, path, ref, repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed := &ParsedResource{
|
||||||
|
Action: provisioning.ResourceActionDelete,
|
||||||
|
Info: &repository.FileInfo{
|
||||||
|
Path: path,
|
||||||
|
Ref: ref,
|
||||||
|
},
|
||||||
|
GVK: schema.GroupVersionKind{
|
||||||
|
Group: FolderResource.Group,
|
||||||
|
Version: FolderResource.Version,
|
||||||
|
Kind: "Folder",
|
||||||
|
},
|
||||||
|
GVR: FolderResource,
|
||||||
|
Repo: provisioning.ResourceRepositoryInfo{
|
||||||
|
Type: repo.Config().Spec.Type,
|
||||||
|
Namespace: repo.Config().Namespace,
|
||||||
|
Name: repo.Config().Name,
|
||||||
|
Title: repo.Config().Spec.Title,
|
||||||
|
},
|
||||||
|
URLs: urls,
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DualReadWriter) getChildren(ctx context.Context, folderPath string, treeEntries []repository.FileTreeEntry) ([]*ParsedResource, []Folder, error) {
|
||||||
|
var resourcesInFolder []repository.FileTreeEntry
|
||||||
|
var foldersInFolder []Folder
|
||||||
|
for _, entry := range treeEntries {
|
||||||
|
// make sure the path is supported (i.e. not ignored by git sync) and that the path is the folder itself or a child of the folder
|
||||||
|
if IsPathSupported(entry.Path) != nil || !safepath.InDir(entry.Path, folderPath) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// folders cannot be parsed as resources, so handle them separately
|
||||||
|
if entry.Blob {
|
||||||
|
resourcesInFolder = append(resourcesInFolder, entry)
|
||||||
|
} else {
|
||||||
|
folder := ParseFolder(entry.Path, r.repo.Config().Name)
|
||||||
|
foldersInFolder = append(foldersInFolder, folder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedResources := make([]*ParsedResource, len(resourcesInFolder))
|
||||||
|
for i, entry := range resourcesInFolder {
|
||||||
|
fileInfo, err := r.repo.Read(ctx, entry.Path, "")
|
||||||
|
if err != nil && !apierrors.IsNotFound(err) {
|
||||||
|
return nil, nil, fmt.Errorf("could not find resource in repository: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := r.parser.Parse(ctx, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("could not parse resource: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedResources[i] = parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedResources, foldersInFolder, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DualReadWriter) deleteChildren(ctx context.Context, childrenResources []*ParsedResource, folders []Folder) error {
|
||||||
|
for _, parsed := range childrenResources {
|
||||||
|
err := parsed.Client.Delete(ctx, parsed.Obj.GetName(), metav1.DeleteOptions{})
|
||||||
|
if err != nil && !apierrors.IsNotFound(err) {
|
||||||
|
return fmt.Errorf("failed to delete nested resource from grafana: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to delete the folders furthest down in the tree first, as folder deletion will fail if there is anything inside of it
|
||||||
|
safepath.SortByDepth(folders, func(f Folder) string { return f.Path }, false)
|
||||||
|
|
||||||
|
for _, f := range folders {
|
||||||
|
err := r.folders.Client().Delete(ctx, f.ID, metav1.DeleteOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete folder from grafana: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@@ -121,7 +122,7 @@ func (r *ResourcesManager) WriteResourceFileFromObject(ctx context.Context, obj
|
|||||||
|
|
||||||
err = r.repo.Write(ctx, fileName, options.Ref, body, commitMessage)
|
err = r.repo.Write(ctx, fileName, options.Ref, body, commitMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to write file: %w", err)
|
return "", fmt.Errorf("failed to write file: %s, %w", fileName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileName, nil
|
return fileName, nil
|
||||||
@@ -206,6 +207,10 @@ func (r *ResourcesManager) RemoveResourceFromFile(ctx context.Context, path stri
|
|||||||
|
|
||||||
err = client.Delete(ctx, objName, metav1.DeleteOptions{})
|
err = client.Delete(ctx, objName, metav1.DeleteOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if apierrors.IsNotFound(err) {
|
||||||
|
return objName, schema.GroupVersionKind{}, nil // Already deleted or simply non-existing, nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
return "", schema.GroupVersionKind{}, fmt.Errorf("failed to delete: %w", err)
|
return "", schema.GroupVersionKind{}, fmt.Errorf("failed to delete: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package resources
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
|
||||||
@@ -95,10 +94,8 @@ func (t *folderTree) Walk(ctx context.Context, fn WalkFunc) error {
|
|||||||
toWalk = append(toWalk, folder)
|
toWalk = append(toWalk, folder)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort by depth of the paths
|
// sort by depth (shallowest first)
|
||||||
sort.Slice(toWalk, func(i, j int) bool {
|
safepath.SortByDepth(toWalk, func(f Folder) string { return f.Path }, true)
|
||||||
return safepath.Depth(toWalk[i].Path) < safepath.Depth(toWalk[j].Path)
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, folder := range toWalk {
|
for _, folder := range toWalk {
|
||||||
if err := fn(ctx, folder, t.tree[folder.ID]); err != nil {
|
if err := fn(ctx, folder, t.tree[folder.ID]); err != nil {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package safepath
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"path"
|
"path"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,3 +44,22 @@ func Split(p string) []string {
|
|||||||
}
|
}
|
||||||
return strings.Split(trimmed, "/")
|
return strings.Split(trimmed, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SortByDepth will sort any resource, by its path depth. You must pass in
|
||||||
|
// a way to get said path. Ties are alphabetical by default.
|
||||||
|
func SortByDepth[T any](items []T, pathExtractor func(T) string, asc bool) {
|
||||||
|
sort.Slice(items, func(i, j int) bool {
|
||||||
|
pathI, pathJ := pathExtractor(items[i]), pathExtractor(items[j])
|
||||||
|
depthI, depthJ := Depth(pathI), Depth(pathJ)
|
||||||
|
|
||||||
|
if depthI == depthJ {
|
||||||
|
// alphabetical by default if depth is the same
|
||||||
|
return pathI < pathJ
|
||||||
|
}
|
||||||
|
|
||||||
|
if asc {
|
||||||
|
return depthI < depthJ
|
||||||
|
}
|
||||||
|
return depthI > depthJ
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user