diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go index d28c96f08aa..847da88fbc5 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go @@ -204,6 +204,9 @@ type JobStatus struct { // Summary of processed actions Summary []*JobResourceSummary `json:"summary,omitempty"` + + // URLs contains URLs for the reference branch or commit if applicable. + URLs *RepositoryURLs `json:"url,omitempty"` } // Convert a JOB to a diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go index e633deda52a..219db2907fb 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go @@ -316,7 +316,7 @@ type ResourceWrapper struct { Repository ResourceRepositoryInfo `json:"repository"` // Typed links for this file (only supported by external systems, github etc) - URLs *ResourceURLs `json:"urls,omitempty"` + URLs *RepositoryURLs `json:"urls,omitempty"` // The modified time in the remote file system Timestamp *metav1.Time `json:"timestamp,omitempty"` @@ -374,14 +374,14 @@ type ResourceRepositoryInfo struct { Name string `json:"name"` } -type ResourceURLs struct { - // A URL pointing to the this file in the repository - SourceURL string `json:"sourceURL,omitempty"` - +type RepositoryURLs struct { // A URL pointing to the repository this lives in RepositoryURL string `json:"repositoryURL,omitempty"` - // A URL that will create a new pull requeset for this branch + // A URL pointing to the file or ref in the repository + SourceURL string `json:"sourceURL,omitempty"` + + // A URL that will create a new pull request for this branch NewPullRequestURL string `json:"newPullRequestURL,omitempty"` // Compare this version to the target branch diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go index 863fa3e0817..868437c9e85 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go @@ -499,6 +499,11 @@ func (in *JobStatus) DeepCopyInto(out *JobStatus) { } } } + if in.URLs != nil { + in, out := &in.URLs, &out.URLs + *out = new(RepositoryURLs) + **out = **in + } return } @@ -790,6 +795,22 @@ func (in *RepositoryStatus) DeepCopy() *RepositoryStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RepositoryURLs) DeepCopyInto(out *RepositoryURLs) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RepositoryURLs. +func (in *RepositoryURLs) DeepCopy() *RepositoryURLs { + if in == nil { + return nil + } + out := new(RepositoryURLs) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RepositoryView) DeepCopyInto(out *RepositoryView) { *out = *in @@ -1023,22 +1044,6 @@ func (in *ResourceType) DeepCopy() *ResourceType { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceURLs) DeepCopyInto(out *ResourceURLs) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceURLs. -func (in *ResourceURLs) DeepCopy() *ResourceURLs { - if in == nil { - return nil - } - out := new(ResourceURLs) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceWrapper) DeepCopyInto(out *ResourceWrapper) { *out = *in @@ -1046,7 +1051,7 @@ func (in *ResourceWrapper) DeepCopyInto(out *ResourceWrapper) { out.Repository = in.Repository if in.URLs != nil { in, out := &in.URLs, &out.URLs - *out = new(ResourceURLs) + *out = new(RepositoryURLs) **out = **in } if in.Timestamp != nil { diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index 50832fe9d6d..0b4c4e42377 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -45,6 +45,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryList": schema_pkg_apis_provisioning_v0alpha1_RepositoryList(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositorySpec": schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryStatus": schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryURLs": schema_pkg_apis_provisioning_v0alpha1_RepositoryURLs(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryView": schema_pkg_apis_provisioning_v0alpha1_RepositoryView(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryViewList": schema_pkg_apis_provisioning_v0alpha1_RepositoryViewList(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceCount": schema_pkg_apis_provisioning_v0alpha1_ResourceCount(ref), @@ -55,7 +56,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo": schema_pkg_apis_provisioning_v0alpha1_ResourceRepositoryInfo(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceStats": schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceType": schema_pkg_apis_provisioning_v0alpha1_ResourceType(ref), - "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceURLs": schema_pkg_apis_provisioning_v0alpha1_ResourceURLs(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceWrapper": schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncJobOptions": schema_pkg_apis_provisioning_v0alpha1_SyncJobOptions(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncOptions": schema_pkg_apis_provisioning_v0alpha1_SyncOptions(ref), @@ -1116,11 +1116,17 @@ func schema_pkg_apis_provisioning_v0alpha1_JobStatus(ref common.ReferenceCallbac }, }, }, + "url": { + SchemaProps: spec.SchemaProps{ + Description: "URLs contains URLs for the reference branch or commit if applicable.", + Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryURLs"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobResourceSummary"}, + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobResourceSummary", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryURLs"}, } } @@ -1651,6 +1657,46 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref common.Reference } } +func schema_pkg_apis_provisioning_v0alpha1_RepositoryURLs(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "repositoryURL": { + SchemaProps: spec.SchemaProps{ + Description: "A URL pointing to the repository this lives in", + Type: []string{"string"}, + Format: "", + }, + }, + "sourceURL": { + SchemaProps: spec.SchemaProps{ + Description: "A URL pointing to the file or ref in the repository", + Type: []string{"string"}, + Format: "", + }, + }, + "newPullRequestURL": { + SchemaProps: spec.SchemaProps{ + Description: "A URL that will create a new pull request for this branch", + Type: []string{"string"}, + Format: "", + }, + }, + "compareURL": { + SchemaProps: spec.SchemaProps{ + Description: "Compare this version to the target branch", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_pkg_apis_provisioning_v0alpha1_RepositoryView(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2216,46 +2262,6 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceType(ref common.ReferenceCall } } -func schema_pkg_apis_provisioning_v0alpha1_ResourceURLs(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "sourceURL": { - SchemaProps: spec.SchemaProps{ - Description: "A URL pointing to the this file in the repository", - Type: []string{"string"}, - Format: "", - }, - }, - "repositoryURL": { - SchemaProps: spec.SchemaProps{ - Description: "A URL pointing to the repository this lives in", - Type: []string{"string"}, - Format: "", - }, - }, - "newPullRequestURL": { - SchemaProps: spec.SchemaProps{ - Description: "A URL that will create a new pull requeset for this branch", - Type: []string{"string"}, - Format: "", - }, - }, - "compareURL": { - SchemaProps: spec.SchemaProps{ - Description: "Compare this version to the target branch", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - } -} - func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2308,7 +2314,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceC "urls": { SchemaProps: spec.SchemaProps{ Description: "Typed links for this file (only supported by external systems, github etc)", - Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceURLs"), + Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryURLs"), }, }, "timestamp": { @@ -2349,7 +2355,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceC }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceObjects", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceURLs", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryURLs", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceObjects", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list index 3060c1f664d..2eca68f79d2 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -18,6 +18,7 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioni API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,TestResults,Errors API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobSpec,PullRequest +API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobStatus,URLs API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ManagerStats,Identity API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,RepositorySpec,GitHub API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,RepositorySpec,GitLab diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobstatus.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobstatus.go index 37a025730e3..ea9228473a5 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobstatus.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobstatus.go @@ -18,6 +18,7 @@ type JobStatusApplyConfiguration struct { Errors []string `json:"errors,omitempty"` Progress *float64 `json:"progress,omitempty"` Summary []*provisioningv0alpha1.JobResourceSummary `json:"summary,omitempty"` + URLs *RepositoryURLsApplyConfiguration `json:"url,omitempty"` } // JobStatusApplyConfiguration constructs a declarative configuration of the JobStatus type for use with @@ -88,3 +89,11 @@ func (b *JobStatusApplyConfiguration) WithSummary(values ...**provisioningv0alph } return b } + +// WithURLs sets the URLs field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URLs field is set to the value of the last call. +func (b *JobStatusApplyConfiguration) WithURLs(value *RepositoryURLsApplyConfiguration) *JobStatusApplyConfiguration { + b.URLs = value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryurls.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryurls.go new file mode 100644 index 00000000000..d18ae864988 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryurls.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// RepositoryURLsApplyConfiguration represents a declarative configuration of the RepositoryURLs type for use +// with apply. +type RepositoryURLsApplyConfiguration struct { + RepositoryURL *string `json:"repositoryURL,omitempty"` + SourceURL *string `json:"sourceURL,omitempty"` + NewPullRequestURL *string `json:"newPullRequestURL,omitempty"` + CompareURL *string `json:"compareURL,omitempty"` +} + +// RepositoryURLsApplyConfiguration constructs a declarative configuration of the RepositoryURLs type for use with +// apply. +func RepositoryURLs() *RepositoryURLsApplyConfiguration { + return &RepositoryURLsApplyConfiguration{} +} + +// WithRepositoryURL sets the RepositoryURL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RepositoryURL field is set to the value of the last call. +func (b *RepositoryURLsApplyConfiguration) WithRepositoryURL(value string) *RepositoryURLsApplyConfiguration { + b.RepositoryURL = &value + return b +} + +// WithSourceURL sets the SourceURL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SourceURL field is set to the value of the last call. +func (b *RepositoryURLsApplyConfiguration) WithSourceURL(value string) *RepositoryURLsApplyConfiguration { + b.SourceURL = &value + return b +} + +// WithNewPullRequestURL sets the NewPullRequestURL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NewPullRequestURL field is set to the value of the last call. +func (b *RepositoryURLsApplyConfiguration) WithNewPullRequestURL(value string) *RepositoryURLsApplyConfiguration { + b.NewPullRequestURL = &value + return b +} + +// WithCompareURL sets the CompareURL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CompareURL field is set to the value of the last call. +func (b *RepositoryURLsApplyConfiguration) WithCompareURL(value string) *RepositoryURLsApplyConfiguration { + b.CompareURL = &value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/utils.go b/apps/provisioning/pkg/generated/applyconfiguration/utils.go index 7c6cb060f78..8725415c377 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/utils.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/utils.go @@ -56,6 +56,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &provisioningv0alpha1.RepositorySpecApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("RepositoryStatus"): return &provisioningv0alpha1.RepositoryStatusApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("RepositoryURLs"): + return &provisioningv0alpha1.RepositoryURLsApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("ResourceCount"): return &provisioningv0alpha1.ResourceCountApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("ResourceRef"): diff --git a/pkg/registry/apis/provisioning/jobs/delete/worker.go b/pkg/registry/apis/provisioning/jobs/delete/worker.go index d55f6300abd..9beff8e953e 100644 --- a/pkg/registry/apis/provisioning/jobs/delete/worker.go +++ b/pkg/registry/apis/provisioning/jobs/delete/worker.go @@ -78,6 +78,15 @@ func (w *Worker) Process(ctx context.Context, repo repository.Repository, job pr return fmt.Errorf("delete files from repository: %w", err) } + // Set RefURLs if the repository supports it and we have a target ref + if opts.Ref != "" { + if repoWithURLs, ok := repo.(repository.RepositoryWithURLs); ok { + if refURLs, urlErr := repoWithURLs.RefURLs(ctx, opts.Ref); urlErr == nil && refURLs != nil { + progress.SetRefURLs(ctx, refURLs) + } + } + } + if opts.Ref == "" { progress.ResetResults() progress.SetMessage(ctx, "pull resources") diff --git a/pkg/registry/apis/provisioning/jobs/delete/worker_test.go b/pkg/registry/apis/provisioning/jobs/delete/worker_test.go index 1da3656f6ce..372ce5d29d5 100644 --- a/pkg/registry/apis/provisioning/jobs/delete/worker_test.go +++ b/pkg/registry/apis/provisioning/jobs/delete/worker_test.go @@ -6,10 +6,11 @@ import ( "testing" "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" @@ -29,41 +30,41 @@ func (m *mockReaderWriter) Delete(ctx context.Context, path, ref, message string // simpleRepository implements only the base Repository interface, not ReaderWriter type simpleRepository struct{} -func (s *simpleRepository) Config() *provisioning.Repository { return nil } -func (s *simpleRepository) Validate() field.ErrorList { return nil } -func (s *simpleRepository) Test(ctx context.Context) (*provisioning.TestResults, error) { +func (s *simpleRepository) Config() *v0alpha1.Repository { return nil } +func (s *simpleRepository) Validate() field.ErrorList { return nil } +func (s *simpleRepository) Test(ctx context.Context) (*v0alpha1.TestResults, error) { return nil, nil } func TestDeleteWorker_IsSupported(t *testing.T) { tests := []struct { name string - job provisioning.Job + job v0alpha1.Job expected bool }{ { name: "delete action is supported", - job: provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, + job: v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, }, }, expected: true, }, { name: "pull action is not supported", - job: provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionPull, + job: v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionPull, }, }, expected: false, }, { name: "push action is not supported", - job: provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionPush, + job: v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionPush, }, }, expected: false, @@ -80,9 +81,9 @@ func TestDeleteWorker_IsSupported(t *testing.T) { } func TestDeleteWorker_ProcessMissingDeleteSettings(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, }, } @@ -92,10 +93,10 @@ func TestDeleteWorker_ProcessMissingDeleteSettings(t *testing.T) { } func TestDeleteWorker_ProcessNotReaderWriter(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ Paths: []string{"test/path"}, }, }, @@ -121,10 +122,10 @@ func TestDeleteWorker_ProcessNotReaderWriter(t *testing.T) { } func TestDeleteWorker_ProcessWrapFnError(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ Paths: []string{"test/path"}, }, }, @@ -144,10 +145,10 @@ func TestDeleteWorker_ProcessWrapFnError(t *testing.T) { } func TestDeleteWorker_ProcessDeleteFilesSuccess(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ Paths: []string{"test/path1", "test/path2"}, Ref: "main", }, @@ -192,10 +193,10 @@ func TestDeleteWorker_ProcessDeleteFilesSuccess(t *testing.T) { } func TestDeleteWorker_ProcessDeleteFilesWithError(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ Paths: []string{"test/path1", "test/path2"}, Ref: "main", }, @@ -230,10 +231,10 @@ func TestDeleteWorker_ProcessDeleteFilesWithError(t *testing.T) { } func TestDeleteWorker_ProcessWithSyncWorker(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ Paths: []string{"test/path"}, }, }, @@ -264,7 +265,7 @@ func TestDeleteWorker_ProcessWithSyncWorker(t *testing.T) { mockProgress.On("ResetResults").Return() mockProgress.On("SetMessage", mock.Anything, "pull resources").Return() - mockSyncWorker.On("Process", mock.Anything, mockRepo, mock.MatchedBy(func(syncJob provisioning.Job) bool { + mockSyncWorker.On("Process", mock.Anything, mockRepo, mock.MatchedBy(func(syncJob v0alpha1.Job) bool { return syncJob.Spec.Pull != nil && !syncJob.Spec.Pull.Incremental }), mockProgress).Return(nil) @@ -274,10 +275,10 @@ func TestDeleteWorker_ProcessWithSyncWorker(t *testing.T) { } func TestDeleteWorker_ProcessSyncWorkerError(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ Paths: []string{"test/path"}, }, }, @@ -357,7 +358,7 @@ func TestDeleteWorker_deleteFiles(t *testing.T) { } mockProgress := jobs.NewMockJobProgressRecorder(t) - opts := provisioning.DeleteJobOptions{ + opts := v0alpha1.DeleteJobOptions{ Ref: "main", } @@ -393,12 +394,12 @@ func TestDeleteWorker_deleteFiles(t *testing.T) { } func TestDeleteWorker_ProcessWithResourceRefs(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ Paths: []string{"test/path1"}, - Resources: []provisioning.ResourceRef{ + Resources: []v0alpha1.ResourceRef{ { Name: "test-dashboard", Kind: "Dashboard", @@ -481,11 +482,11 @@ func TestDeleteWorker_ProcessWithResourceRefs(t *testing.T) { } func TestDeleteWorker_ProcessResourceRefsOnly(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ - Resources: []provisioning.ResourceRef{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ + Resources: []v0alpha1.ResourceRef{ { Name: "test-dashboard", Kind: "Dashboard", @@ -536,11 +537,11 @@ func TestDeleteWorker_ProcessResourceRefsOnly(t *testing.T) { } func TestDeleteWorker_ProcessResourceResolutionError(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ - Resources: []provisioning.ResourceRef{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ + Resources: []v0alpha1.ResourceRef{ { Name: "nonexistent-dashboard", Kind: "Dashboard", @@ -591,7 +592,7 @@ func TestDeleteWorker_ProcessResourceResolutionError(t *testing.T) { mockProgress.On("SetMessage", mock.Anything, "pull resources").Return() mockSyncWorker := jobs.NewMockWorker(t) - mockSyncWorker.On("Process", mock.Anything, mockRepo, mock.MatchedBy(func(syncJob provisioning.Job) bool { + mockSyncWorker.On("Process", mock.Anything, mockRepo, mock.MatchedBy(func(syncJob v0alpha1.Job) bool { return syncJob.Spec.Pull != nil && !syncJob.Spec.Pull.Incremental }), mockProgress).Return(nil) @@ -601,11 +602,11 @@ func TestDeleteWorker_ProcessResourceResolutionError(t *testing.T) { } func TestDeleteWorker_ProcessResourcesFactoryError(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ - Resources: []provisioning.ResourceRef{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ + Resources: []v0alpha1.ResourceRef{ { Name: "test-dashboard", Kind: "Dashboard", @@ -640,11 +641,11 @@ func TestDeleteWorker_ProcessResourcesFactoryError(t *testing.T) { } func TestDeleteWorker_ProcessResourceRefsNotReaderWriter(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ - Resources: []provisioning.ResourceRef{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ + Resources: []v0alpha1.ResourceRef{ { Name: "test-dashboard", Kind: "Dashboard", @@ -676,11 +677,11 @@ func TestDeleteWorker_ProcessResourceRefsNotReaderWriter(t *testing.T) { } func TestDeleteWorker_ProcessResourceResolutionTooManyErrors(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ - Resources: []provisioning.ResourceRef{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ + Resources: []v0alpha1.ResourceRef{ { Name: "nonexistent-dashboard", Kind: "Dashboard", @@ -729,11 +730,11 @@ func TestDeleteWorker_ProcessResourceResolutionTooManyErrors(t *testing.T) { } func TestDeleteWorker_ProcessMixedResourcesWithPartialFailure(t *testing.T) { - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ - Resources: []provisioning.ResourceRef{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ + Resources: []v0alpha1.ResourceRef{ { Name: "valid-dashboard", Kind: "Dashboard", @@ -827,13 +828,13 @@ func TestDeleteWorker_ProcessMixedResourcesWithPartialFailure(t *testing.T) { func TestDeleteWorker_ProcessWithPathDeduplication(t *testing.T) { // Test that duplicate paths from explicit paths and resource resolution are deduplicated - job := provisioning.Job{ - Spec: provisioning.JobSpec{ - Action: provisioning.JobActionDelete, - Delete: &provisioning.DeleteJobOptions{ + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ Ref: "main", // Add ref to avoid sync worker execution Paths: []string{"dashboards/test-dashboard.json", "folders/test-folder/"}, // Explicit paths - Resources: []provisioning.ResourceRef{ + Resources: []v0alpha1.ResourceRef{ { Name: "test-dashboard", // This will resolve to "dashboards/test-dashboard.json" (duplicate) Kind: "Dashboard", @@ -984,3 +985,167 @@ func TestDeduplicatePaths(t *testing.T) { }) } } + +func TestDeleteWorker_RefURLsSetWithRef(t *testing.T) { + mockRepoWithURLs := repository.NewMockRepositoryWithURLs(t) + config := &v0alpha1.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: v0alpha1.RepositorySpec{ + Type: v0alpha1.GitHubRepositoryType, + }, + } + mockRepoWithURLs.On("Config").Return(config).Maybe() // Config may be called multiple times + + // Mock RefURLs method to return expected URLs + expectedRefURLs := &v0alpha1.RepositoryURLs{ + SourceURL: "https://github.com/grafana/grafana/tree/feature-branch", + CompareURL: "https://github.com/grafana/grafana/compare/main...feature-branch", + NewPullRequestURL: "https://github.com/grafana/grafana/compare/main...feature-branch?quick_pull=1&labels=grafana", + } + mockRepoWithURLs.On("RefURLs", mock.Anything, "feature-branch").Return(expectedRefURLs, nil) + + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockProgress.On("SetTotal", mock.Anything, 1).Once() + mockProgress.On("StrictMaxErrors", 1).Once() + mockProgress.On("SetMessage", mock.Anything, "Deleting test.json").Once() + mockProgress.On("Record", mock.Anything, mock.Anything).Once() + mockProgress.On("TooManyErrors").Return(nil).Once() + mockProgress.On("SetRefURLs", mock.Anything, expectedRefURLs).Once() + + mockReaderWriter := repository.NewMockReaderWriter(t) + mockReaderWriter.On("Delete", mock.Anything, "test.json", "feature-branch", "Delete test.json").Return(nil) + + mockWrapFn := repository.NewMockWrapWithStageFn(t) + mockWrapFn.On("Execute", mock.Anything, mockRepoWithURLs, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, opts repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(mockReaderWriter, true) + }) + + mockResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) + + job := v0alpha1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "test-job"}, + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ + Ref: "feature-branch", + Paths: []string{"test.json"}, + }, + }, + } + + worker := NewWorker(nil, mockWrapFn.Execute, mockResourcesFactory) + err := worker.Process(context.Background(), mockRepoWithURLs, job, mockProgress) + require.NoError(t, err) + + // Verify that SetRefURLs was called with the expected RefURLs + mockProgress.AssertExpectations(t) + mockRepoWithURLs.AssertExpectations(t) +} + +func TestDeleteWorker_RefURLsNotSetWithoutRef(t *testing.T) { + mockRepoWithURLs := repository.NewMockRepositoryWithURLs(t) + config := &v0alpha1.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: v0alpha1.RepositorySpec{ + Type: v0alpha1.GitHubRepositoryType, + }, + } + mockRepoWithURLs.On("Config").Return(config).Maybe() // Config may be called multiple times + + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockProgress.On("SetTotal", mock.Anything, 1).Once() + mockProgress.On("StrictMaxErrors", 1).Once() + mockProgress.On("SetMessage", mock.Anything, "Deleting test.json").Once() + mockProgress.On("Record", mock.Anything, mock.Anything).Once() + mockProgress.On("TooManyErrors").Return(nil).Once() + mockProgress.On("ResetResults").Once() + mockProgress.On("SetMessage", mock.Anything, "pull resources").Once() + // SetRefURLs should NOT be called since no ref is specified + + mockReaderWriter := repository.NewMockReaderWriter(t) + mockReaderWriter.On("Delete", mock.Anything, "test.json", "", "Delete test.json").Return(nil) + + mockWrapFn := repository.NewMockWrapWithStageFn(t) + mockWrapFn.On("Execute", mock.Anything, mockRepoWithURLs, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, opts repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(mockReaderWriter, true) + }) + + mockSyncWorker := jobs.NewMockWorker(t) + mockSyncWorker.On("Process", mock.Anything, mockRepoWithURLs, mock.Anything, mockProgress).Return(nil) + + mockResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) + + job := v0alpha1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "test-job"}, + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ + // No ref specified + Paths: []string{"test.json"}, + }, + }, + } + + worker := NewWorker(mockSyncWorker, mockWrapFn.Execute, mockResourcesFactory) + err := worker.Process(context.Background(), mockRepoWithURLs, job, mockProgress) + require.NoError(t, err) + + // Verify that SetRefURLs was NOT called since no ref was specified + mockProgress.AssertExpectations(t) +} + +func TestDeleteWorker_RefURLsNotSetForNonURLRepository(t *testing.T) { + mockRepo := repository.NewMockRepository(t) + config := &v0alpha1.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: v0alpha1.RepositorySpec{ + Type: v0alpha1.GitRepositoryType, // Regular git repo, not GitHub + }, + } + mockRepo.On("Config").Return(config).Maybe() // Config may be called multiple times + + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockProgress.On("SetTotal", mock.Anything, 1).Once() + mockProgress.On("StrictMaxErrors", 1).Once() + mockProgress.On("SetMessage", mock.Anything, "Deleting test.json").Once() + mockProgress.On("Record", mock.Anything, mock.Anything).Once() + mockProgress.On("TooManyErrors").Return(nil).Once() + // SetRefURLs should NOT be called since repo doesn't support URLs + + mockReaderWriter := repository.NewMockReaderWriter(t) + mockReaderWriter.On("Delete", mock.Anything, "test.json", "feature-branch", "Delete test.json").Return(nil) + + mockWrapFn := repository.NewMockWrapWithStageFn(t) + mockWrapFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, opts repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(mockReaderWriter, true) + }) + + mockResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) + + job := v0alpha1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "test-job"}, + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionDelete, + Delete: &v0alpha1.DeleteJobOptions{ + Ref: "feature-branch", + Paths: []string{"test.json"}, + }, + }, + } + + worker := NewWorker(nil, mockWrapFn.Execute, mockResourcesFactory) + err := worker.Process(context.Background(), mockRepo, job, mockProgress) + require.NoError(t, err) + + // Verify that SetRefURLs was NOT called since repo doesn't support URLs + mockProgress.AssertExpectations(t) +} diff --git a/pkg/registry/apis/provisioning/jobs/export/worker.go b/pkg/registry/apis/provisioning/jobs/export/worker.go index 524b01dce4c..df394cfcd00 100644 --- a/pkg/registry/apis/provisioning/jobs/export/worker.go +++ b/pkg/registry/apis/provisioning/jobs/export/worker.go @@ -88,5 +88,16 @@ func (r *ExportWorker) Process(ctx context.Context, repo repository.Repository, return r.exportFn(ctx, cfg.Name, *options, clients, repositoryResources, progress) } - return r.wrapWithStageFn(ctx, repo, cloneOptions, fn) + err := r.wrapWithStageFn(ctx, repo, cloneOptions, fn) + + // Set RefURLs if the repository supports it and we have a target branch + if options.Branch != "" { + if repoWithURLs, ok := repo.(repository.RepositoryWithURLs); ok { + if refURLs, urlErr := repoWithURLs.RefURLs(ctx, options.Branch); urlErr == nil && refURLs != nil { + progress.SetRefURLs(ctx, refURLs) + } + } + } + + return err } diff --git a/pkg/registry/apis/provisioning/jobs/export/worker_test.go b/pkg/registry/apis/provisioning/jobs/export/worker_test.go index d78b0ec5b8b..36e50e25494 100644 --- a/pkg/registry/apis/provisioning/jobs/export/worker_test.go +++ b/pkg/registry/apis/provisioning/jobs/export/worker_test.go @@ -549,3 +549,181 @@ func TestExportWorker_ProcessGitRepositoryExportFnError(t *testing.T) { err := r.Process(context.Background(), mockRepo, job, mockProgress) require.EqualError(t, err, "export failed") } + +func TestExportWorker_RefURLsSetWithBranch(t *testing.T) { + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionPush, + Push: &v0alpha1.ExportJobOptions{ + Branch: "feature-branch", + Message: "test commit", + }, + }, + } + + // Create a repository that implements both Repository and RepositoryWithURLs + mockRepoWithURLs := repository.NewMockRepositoryWithURLs(t) + + mockRepoWithURLs.On("Config").Return(&v0alpha1.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: v0alpha1.RepositorySpec{ + Type: v0alpha1.GitHubRepositoryType, + Workflows: []v0alpha1.Workflow{v0alpha1.WriteWorkflow, v0alpha1.BranchWorkflow}, + }, + }) + + // Mock RefURLs method to return expected URLs + expectedRefURLs := &v0alpha1.RepositoryURLs{ + SourceURL: "https://github.com/grafana/grafana/tree/feature-branch", + CompareURL: "https://github.com/grafana/grafana/compare/main...feature-branch", + NewPullRequestURL: "https://github.com/grafana/grafana/compare/main...feature-branch?quick_pull=1&labels=grafana", + } + mockRepoWithURLs.On("RefURLs", mock.Anything, "feature-branch").Return(expectedRefURLs, nil) + + // Mock progress recorder to expect SetRefURLs call + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockProgress.On("SetRefURLs", mock.Anything, expectedRefURLs).Once() + + // Mock other dependencies + mockClients := resources.NewMockClientFactory(t) + mockResourceClients := resources.NewMockResourceClients(t) + mockClients.On("Clients", mock.Anything, "test-namespace").Return(mockResourceClients, nil) + + mockRepoResources := resources.NewMockRepositoryResourcesFactory(t) + mockRepoResourcesClient := resources.NewMockRepositoryResources(t) + mockRepoResources.On("Client", mock.Anything, mock.Anything).Return(mockRepoResourcesClient, nil) + + mockExportFn := NewMockExportFn(t) + mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + + // Mock the ReaderWriter interface that the export function expects + mockReaderWriter := repository.NewMockReaderWriter(t) + + mockStageFn := NewMockWrapWithStageFn(t) + mockStageFn.On("Execute", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error { + // The staging function needs to call the inner function with a ReaderWriter + return fn(mockReaderWriter, true) + }) + + r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockStageFn.Execute) + err := r.Process(context.Background(), mockRepoWithURLs, job, mockProgress) + require.NoError(t, err) + + // Verify that SetRefURLs was called with the expected RefURLs + mockProgress.AssertExpectations(t) + mockRepoWithURLs.AssertExpectations(t) +} + +func TestExportWorker_RefURLsNotSetWithoutBranch(t *testing.T) { + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionPush, + Push: &v0alpha1.ExportJobOptions{ + Message: "test commit", + // No branch specified + }, + }, + } + + mockRepoWithURLs := repository.NewMockRepositoryWithURLs(t) + + mockRepoWithURLs.On("Config").Return(&v0alpha1.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: v0alpha1.RepositorySpec{ + Type: v0alpha1.GitHubRepositoryType, + Workflows: []v0alpha1.Workflow{v0alpha1.WriteWorkflow}, + }, + }) + + // Mock progress recorder - SetRefURLs should NOT be called + mockProgress := jobs.NewMockJobProgressRecorder(t) + // Explicitly NOT expecting SetRefURLs call + + // Mock other dependencies + mockClients := resources.NewMockClientFactory(t) + mockResourceClients := resources.NewMockResourceClients(t) + mockClients.On("Clients", mock.Anything, "test-namespace").Return(mockResourceClients, nil) + + mockRepoResources := resources.NewMockRepositoryResourcesFactory(t) + mockRepoResourcesClient := resources.NewMockRepositoryResources(t) + mockRepoResources.On("Client", mock.Anything, mock.Anything).Return(mockRepoResourcesClient, nil) + + mockExportFn := NewMockExportFn(t) + mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + + mockReaderWriter := repository.NewMockReaderWriter(t) + + mockStageFn := NewMockWrapWithStageFn(t) + mockStageFn.On("Execute", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(mockReaderWriter, true) + }) + + r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockStageFn.Execute) + err := r.Process(context.Background(), mockRepoWithURLs, job, mockProgress) + require.NoError(t, err) + + // Verify that SetRefURLs was NOT called since no branch was specified + mockProgress.AssertExpectations(t) +} + +func TestExportWorker_RefURLsNotSetForNonURLRepository(t *testing.T) { + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionPush, + Push: &v0alpha1.ExportJobOptions{ + Branch: "feature-branch", + Message: "test commit", + }, + }, + } + + // Use a regular Repository that doesn't implement RepositoryWithURLs + mockRepo := repository.NewMockRepository(t) + + mockRepo.On("Config").Return(&v0alpha1.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: v0alpha1.RepositorySpec{ + Type: v0alpha1.GitRepositoryType, // Regular git repo, not GitHub + Workflows: []v0alpha1.Workflow{v0alpha1.WriteWorkflow, v0alpha1.BranchWorkflow}, + }, + }) + + // Mock progress recorder - SetRefURLs should NOT be called + mockProgress := jobs.NewMockJobProgressRecorder(t) + // Explicitly NOT expecting SetRefURLs call + + // Mock other dependencies + mockClients := resources.NewMockClientFactory(t) + mockResourceClients := resources.NewMockResourceClients(t) + mockClients.On("Clients", mock.Anything, "test-namespace").Return(mockResourceClients, nil) + + mockRepoResources := resources.NewMockRepositoryResourcesFactory(t) + mockRepoResourcesClient := resources.NewMockRepositoryResources(t) + mockRepoResources.On("Client", mock.Anything, mock.Anything).Return(mockRepoResourcesClient, nil) + + mockExportFn := NewMockExportFn(t) + mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + + mockReaderWriter := repository.NewMockReaderWriter(t) + + mockStageFn := NewMockWrapWithStageFn(t) + mockStageFn.On("Execute", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(mockReaderWriter, true) + }) + + r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockStageFn.Execute) + err := r.Process(context.Background(), mockRepo, job, mockProgress) + require.NoError(t, err) + + // Verify that SetRefURLs was NOT called since repo doesn't support URLs + mockProgress.AssertExpectations(t) +} diff --git a/pkg/registry/apis/provisioning/jobs/job_progress_recorder_mock.go b/pkg/registry/apis/provisioning/jobs/job_progress_recorder_mock.go index be0b6d3d9b0..3cefca9e5af 100644 --- a/pkg/registry/apis/provisioning/jobs/job_progress_recorder_mock.go +++ b/pkg/registry/apis/provisioning/jobs/job_progress_recorder_mock.go @@ -203,6 +203,40 @@ func (_c *MockJobProgressRecorder_SetMessage_Call) RunAndReturn(run func(context return _c } +// SetRefURLs provides a mock function with given fields: ctx, refURLs +func (_m *MockJobProgressRecorder) SetRefURLs(ctx context.Context, refURLs *v0alpha1.RepositoryURLs) { + _m.Called(ctx, refURLs) +} + +// MockJobProgressRecorder_SetRefURLs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetRefURLs' +type MockJobProgressRecorder_SetRefURLs_Call struct { + *mock.Call +} + +// SetRefURLs is a helper method to define mock.On call +// - ctx context.Context +// - refURLs *v0alpha1.RepositoryURLs +func (_e *MockJobProgressRecorder_Expecter) SetRefURLs(ctx interface{}, refURLs interface{}) *MockJobProgressRecorder_SetRefURLs_Call { + return &MockJobProgressRecorder_SetRefURLs_Call{Call: _e.mock.On("SetRefURLs", ctx, refURLs)} +} + +func (_c *MockJobProgressRecorder_SetRefURLs_Call) Run(run func(ctx context.Context, refURLs *v0alpha1.RepositoryURLs)) *MockJobProgressRecorder_SetRefURLs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*v0alpha1.RepositoryURLs)) + }) + return _c +} + +func (_c *MockJobProgressRecorder_SetRefURLs_Call) Return() *MockJobProgressRecorder_SetRefURLs_Call { + _c.Call.Return() + return _c +} + +func (_c *MockJobProgressRecorder_SetRefURLs_Call) RunAndReturn(run func(context.Context, *v0alpha1.RepositoryURLs)) *MockJobProgressRecorder_SetRefURLs_Call { + _c.Run(run) + return _c +} + // SetTotal provides a mock function with given fields: ctx, total func (_m *MockJobProgressRecorder) SetTotal(ctx context.Context, total int) { _m.Called(ctx, total) diff --git a/pkg/registry/apis/provisioning/jobs/move/worker.go b/pkg/registry/apis/provisioning/jobs/move/worker.go index f0b53c8d404..5e409db7e4b 100644 --- a/pkg/registry/apis/provisioning/jobs/move/worker.go +++ b/pkg/registry/apis/provisioning/jobs/move/worker.go @@ -89,6 +89,15 @@ func (w *Worker) Process(ctx context.Context, repo repository.Repository, job pr return fmt.Errorf("move files in repository: %w", err) } + // Set RefURLs if the repository supports it and we have a target ref + if opts.Ref != "" { + if repoWithURLs, ok := repo.(repository.RepositoryWithURLs); ok { + if refURLs, urlErr := repoWithURLs.RefURLs(ctx, opts.Ref); urlErr == nil && refURLs != nil { + progress.SetRefURLs(ctx, refURLs) + } + } + } + if opts.Ref == "" { progress.ResetResults() progress.SetMessage(ctx, "pull resources") diff --git a/pkg/registry/apis/provisioning/jobs/move/worker_test.go b/pkg/registry/apis/provisioning/jobs/move/worker_test.go index c2e57a3552b..a728bd5d9f0 100644 --- a/pkg/registry/apis/provisioning/jobs/move/worker_test.go +++ b/pkg/registry/apis/provisioning/jobs/move/worker_test.go @@ -833,3 +833,170 @@ func TestMoveWorker_deduplicatePaths(t *testing.T) { }) } } + +func TestMoveWorker_RefURLsSetWithRef(t *testing.T) { + mockRepoWithURLs := repository.NewMockRepositoryWithURLs(t) + config := &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: provisioning.RepositorySpec{ + Type: provisioning.GitHubRepositoryType, + }, + } + mockRepoWithURLs.On("Config").Return(config).Maybe() // Config may be called multiple times + + // Mock RefURLs method to return expected URLs + expectedRefURLs := &provisioning.RepositoryURLs{ + SourceURL: "https://github.com/grafana/grafana/tree/feature-branch", + CompareURL: "https://github.com/grafana/grafana/compare/main...feature-branch", + NewPullRequestURL: "https://github.com/grafana/grafana/compare/main...feature-branch?quick_pull=1&labels=grafana", + } + mockRepoWithURLs.On("RefURLs", mock.Anything, "feature-branch").Return(expectedRefURLs, nil) + + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockProgress.On("SetTotal", mock.Anything, 1).Once() + mockProgress.On("StrictMaxErrors", 1).Once() + mockProgress.On("SetMessage", mock.Anything, "Moving test.json to target/test.json").Once() + mockProgress.On("Record", mock.Anything, mock.Anything).Once() + mockProgress.On("TooManyErrors").Return(nil).Once() + mockProgress.On("SetRefURLs", mock.Anything, expectedRefURLs).Once() + + mockReaderWriter := repository.NewMockReaderWriter(t) + mockReaderWriter.On("Move", mock.Anything, "test.json", "target/test.json", "feature-branch", "Move test.json to target/test.json").Return(nil) + + mockWrapFn := repository.NewMockWrapWithStageFn(t) + mockWrapFn.On("Execute", mock.Anything, mockRepoWithURLs, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, opts repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(mockReaderWriter, true) + }) + + mockResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) + + job := provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "test-job"}, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMove, + Move: &provisioning.MoveJobOptions{ + Ref: "feature-branch", + Paths: []string{"test.json"}, + TargetPath: "target/", + }, + }, + } + + worker := NewWorker(nil, mockWrapFn.Execute, mockResourcesFactory) + err := worker.Process(context.Background(), mockRepoWithURLs, job, mockProgress) + require.NoError(t, err) + + // Verify that SetRefURLs was called with the expected RefURLs + mockProgress.AssertExpectations(t) + mockRepoWithURLs.AssertExpectations(t) +} + +func TestMoveWorker_RefURLsNotSetWithoutRef(t *testing.T) { + mockRepoWithURLs := repository.NewMockRepositoryWithURLs(t) + config := &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: provisioning.RepositorySpec{ + Type: provisioning.GitHubRepositoryType, + }, + } + mockRepoWithURLs.On("Config").Return(config).Maybe() // Config may be called multiple times + + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockProgress.On("SetTotal", mock.Anything, 1).Once() + mockProgress.On("StrictMaxErrors", 1).Once() + mockProgress.On("SetMessage", mock.Anything, "Moving test.json to target/test.json").Once() + mockProgress.On("Record", mock.Anything, mock.Anything).Once() + mockProgress.On("TooManyErrors").Return(nil).Once() + mockProgress.On("ResetResults").Once() + mockProgress.On("SetMessage", mock.Anything, "pull resources").Once() + // SetRefURLs should NOT be called since no ref is specified + + mockReaderWriter := repository.NewMockReaderWriter(t) + mockReaderWriter.On("Move", mock.Anything, "test.json", "target/test.json", "", "Move test.json to target/test.json").Return(nil) + + mockWrapFn := repository.NewMockWrapWithStageFn(t) + mockWrapFn.On("Execute", mock.Anything, mockRepoWithURLs, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, opts repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(mockReaderWriter, true) + }) + + mockSyncWorker := jobs.NewMockWorker(t) + mockSyncWorker.On("Process", mock.Anything, mockRepoWithURLs, mock.Anything, mockProgress).Return(nil) + + mockResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) + + job := provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "test-job"}, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMove, + Move: &provisioning.MoveJobOptions{ + // No ref specified + Paths: []string{"test.json"}, + TargetPath: "target/", + }, + }, + } + + worker := NewWorker(mockSyncWorker, mockWrapFn.Execute, mockResourcesFactory) + err := worker.Process(context.Background(), mockRepoWithURLs, job, mockProgress) + require.NoError(t, err) + + // Verify that SetRefURLs was NOT called since no ref was specified + mockProgress.AssertExpectations(t) +} + +func TestMoveWorker_RefURLsNotSetForNonURLRepository(t *testing.T) { + mockRepo := repository.NewMockRepository(t) + config := &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, // Regular git repo, not GitHub + }, + } + mockRepo.On("Config").Return(config).Maybe() // Config may be called multiple times + + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockProgress.On("SetTotal", mock.Anything, 1).Once() + mockProgress.On("StrictMaxErrors", 1).Once() + mockProgress.On("SetMessage", mock.Anything, "Moving test.json to target/test.json").Once() + mockProgress.On("Record", mock.Anything, mock.Anything).Once() + mockProgress.On("TooManyErrors").Return(nil).Once() + // SetRefURLs should NOT be called since repo doesn't support URLs + + mockReaderWriter := repository.NewMockReaderWriter(t) + mockReaderWriter.On("Move", mock.Anything, "test.json", "target/test.json", "feature-branch", "Move test.json to target/test.json").Return(nil) + + mockWrapFn := repository.NewMockWrapWithStageFn(t) + mockWrapFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, opts repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(mockReaderWriter, true) + }) + + mockResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) + + job := provisioning.Job{ + ObjectMeta: metav1.ObjectMeta{Name: "test-job"}, + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionMove, + Move: &provisioning.MoveJobOptions{ + Ref: "feature-branch", + Paths: []string{"test.json"}, + TargetPath: "target/", + }, + }, + } + + worker := NewWorker(nil, mockWrapFn.Execute, mockResourcesFactory) + err := worker.Process(context.Background(), mockRepo, job, mockProgress) + require.NoError(t, err) + + // Verify that SetRefURLs was NOT called since repo doesn't support URLs + mockProgress.AssertExpectations(t) +} diff --git a/pkg/registry/apis/provisioning/jobs/progress.go b/pkg/registry/apis/provisioning/jobs/progress.go index f0266471837..17d0fb60248 100644 --- a/pkg/registry/apis/provisioning/jobs/progress.go +++ b/pkg/registry/apis/provisioning/jobs/progress.go @@ -53,6 +53,7 @@ type jobProgressRecorder struct { resultCount int errorCount int errors []string + refURLs *provisioning.RepositoryURLs notifyImmediatelyFn ProgressFn maybeNotifyFn ProgressFn summaries map[string]*provisioning.JobResourceSummary @@ -117,6 +118,18 @@ func (r *jobProgressRecorder) SetFinalMessage(ctx context.Context, msg string) { logging.FromContext(ctx).Info("job final message", "message", msg) } +func (r *jobProgressRecorder) SetRefURLs(ctx context.Context, refURLs *provisioning.RepositoryURLs) { + r.mu.Lock() + r.refURLs = refURLs + r.mu.Unlock() + + if refURLs != nil { + logging.FromContext(ctx).Debug("job ref URLs set", "sourceURL", refURLs.SourceURL, "compareURL", refURLs.CompareURL, "newPullRequestURL", refURLs.NewPullRequestURL) + } else { + logging.FromContext(ctx).Debug("job ref URLs cleared") + } +} + func (r *jobProgressRecorder) SetTotal(ctx context.Context, total int) { r.mu.Lock() r.total = total @@ -253,6 +266,7 @@ func (r *jobProgressRecorder) Complete(ctx context.Context, err error) provision jobStatus.Summary = r.summary() jobStatus.Errors = r.errors + jobStatus.URLs = r.refURLs // Check for errors during execution if len(jobStatus.Errors) > 0 && jobStatus.State != provisioning.JobStateError { diff --git a/pkg/registry/apis/provisioning/jobs/progress_test.go b/pkg/registry/apis/provisioning/jobs/progress_test.go new file mode 100644 index 00000000000..caf44c767ff --- /dev/null +++ b/pkg/registry/apis/provisioning/jobs/progress_test.go @@ -0,0 +1,85 @@ +package jobs + +import ( + "context" + "testing" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestJobProgressRecorderSetRefURLs(t *testing.T) { + ctx := context.Background() + + // Create a progress recorder + mockProgressFn := func(ctx context.Context, status provisioning.JobStatus) error { + return nil + } + recorder := newJobProgressRecorder(mockProgressFn).(*jobProgressRecorder) + + // Test setting RefURLs + expectedRefURLs := &provisioning.RepositoryURLs{ + SourceURL: "https://github.com/grafana/grafana/tree/feature-branch", + CompareURL: "https://github.com/grafana/grafana/compare/main...feature-branch", + NewPullRequestURL: "https://github.com/grafana/grafana/compare/main...feature-branch?quick_pull=1&labels=grafana", + } + + recorder.SetRefURLs(ctx, expectedRefURLs) + + // Verify RefURLs are stored + recorder.mu.RLock() + assert.Equal(t, expectedRefURLs, recorder.refURLs) + recorder.mu.RUnlock() + + // Test that RefURLs are included in the final status + finalStatus := recorder.Complete(ctx, nil) + assert.Equal(t, expectedRefURLs, finalStatus.URLs) +} + +func TestJobProgressRecorderSetRefURLsNil(t *testing.T) { + ctx := context.Background() + + // Create a progress recorder + mockProgressFn := func(ctx context.Context, status provisioning.JobStatus) error { + return nil + } + recorder := newJobProgressRecorder(mockProgressFn).(*jobProgressRecorder) + + // Test setting nil RefURLs + recorder.SetRefURLs(ctx, nil) + + // Verify nil RefURLs are stored + recorder.mu.RLock() + assert.Nil(t, recorder.refURLs) + recorder.mu.RUnlock() + + // Test that nil RefURLs are included in the final status + finalStatus := recorder.Complete(ctx, nil) + assert.Nil(t, finalStatus.URLs) +} + +func TestJobProgressRecorderCompleteIncludesRefURLs(t *testing.T) { + ctx := context.Background() + + // Create a progress recorder + mockProgressFn := func(ctx context.Context, status provisioning.JobStatus) error { + return nil + } + recorder := newJobProgressRecorder(mockProgressFn).(*jobProgressRecorder) + + // Set some RefURLs + refURLs := &provisioning.RepositoryURLs{ + SourceURL: "https://github.com/grafana/grafana/tree/test-branch", + } + recorder.SetRefURLs(ctx, refURLs) + + // Complete the job + finalStatus := recorder.Complete(ctx, nil) + + // Verify the final status includes RefURLs + require.NotNil(t, finalStatus.URLs) + assert.Equal(t, refURLs.SourceURL, finalStatus.URLs.SourceURL) + assert.Equal(t, provisioning.JobStateSuccess, finalStatus.State) + assert.Equal(t, "completed successfully", finalStatus.Message) +} diff --git a/pkg/registry/apis/provisioning/jobs/queue.go b/pkg/registry/apis/provisioning/jobs/queue.go index 3a9757d93a1..f7e2a60e4f5 100644 --- a/pkg/registry/apis/provisioning/jobs/queue.go +++ b/pkg/registry/apis/provisioning/jobs/queue.go @@ -25,6 +25,7 @@ type JobProgressRecorder interface { SetTotal(ctx context.Context, total int) TooManyErrors() error StrictMaxErrors(maxErrors int) + SetRefURLs(ctx context.Context, refURLs *provisioning.RepositoryURLs) Complete(ctx context.Context, err error) provisioning.JobStatus } diff --git a/pkg/registry/apis/provisioning/jobs/store_mock.go b/pkg/registry/apis/provisioning/jobs/store_mock.go index 79d91719605..aebcac9bbbe 100644 --- a/pkg/registry/apis/provisioning/jobs/store_mock.go +++ b/pkg/registry/apis/provisioning/jobs/store_mock.go @@ -182,6 +182,112 @@ func (_c *MockStore_Complete_Call) RunAndReturn(run func(context.Context, *v0alp return _c } +// Get provides a mock function with given fields: ctx, name +func (_m *MockStore) Get(ctx context.Context, name string) (*v0alpha1.Job, error) { + ret := _m.Called(ctx, name) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 *v0alpha1.Job + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*v0alpha1.Job, error)); ok { + return rf(ctx, name) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *v0alpha1.Job); ok { + r0 = rf(ctx, name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0alpha1.Job) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockStore_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type MockStore_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - ctx context.Context +// - name string +func (_e *MockStore_Expecter) Get(ctx interface{}, name interface{}) *MockStore_Get_Call { + return &MockStore_Get_Call{Call: _e.mock.On("Get", ctx, name)} +} + +func (_c *MockStore_Get_Call) Run(run func(ctx context.Context, name string)) *MockStore_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockStore_Get_Call) Return(_a0 *v0alpha1.Job, _a1 error) *MockStore_Get_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockStore_Get_Call) RunAndReturn(run func(context.Context, string) (*v0alpha1.Job, error)) *MockStore_Get_Call { + _c.Call.Return(run) + return _c +} + +// RenewLease provides a mock function with given fields: ctx, job +func (_m *MockStore) RenewLease(ctx context.Context, job *v0alpha1.Job) error { + ret := _m.Called(ctx, job) + + if len(ret) == 0 { + panic("no return value specified for RenewLease") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Job) error); ok { + r0 = rf(ctx, job) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockStore_RenewLease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RenewLease' +type MockStore_RenewLease_Call struct { + *mock.Call +} + +// RenewLease is a helper method to define mock.On call +// - ctx context.Context +// - job *v0alpha1.Job +func (_e *MockStore_Expecter) RenewLease(ctx interface{}, job interface{}) *MockStore_RenewLease_Call { + return &MockStore_RenewLease_Call{Call: _e.mock.On("RenewLease", ctx, job)} +} + +func (_c *MockStore_RenewLease_Call) Run(run func(ctx context.Context, job *v0alpha1.Job)) *MockStore_RenewLease_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*v0alpha1.Job)) + }) + return _c +} + +func (_c *MockStore_RenewLease_Call) Return(_a0 error) *MockStore_RenewLease_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockStore_RenewLease_Call) RunAndReturn(run func(context.Context, *v0alpha1.Job) error) *MockStore_RenewLease_Call { + _c.Call.Return(run) + return _c +} + // Update provides a mock function with given fields: ctx, job func (_m *MockStore) Update(ctx context.Context, job *v0alpha1.Job) (*v0alpha1.Job, error) { ret := _m.Called(ctx, job) diff --git a/pkg/registry/apis/provisioning/repository/git/staged.go b/pkg/registry/apis/provisioning/repository/git/staged.go index f55eb709341..01a375ce7f4 100644 --- a/pkg/registry/apis/provisioning/repository/git/staged.go +++ b/pkg/registry/apis/provisioning/repository/git/staged.go @@ -31,13 +31,9 @@ func NewStagedGitRepository(ctx context.Context, repo *gitRepository, opts repos branch = repo.gitConfig.Branch } - ref, err := repo.client.GetRef(ctx, "refs/heads/"+branch) + ref, err := repo.ensureBranchExists(ctx, branch) if err != nil { - // TODO: opts.CreateIfNotExists doesn't make sense in the context of the staged repository - // because we only support the branch that is passed in. - // we should probably add branch to the repository.CloneOptions which should be repurposed - // as some kind of branch creation options. - return nil, err + return nil, fmt.Errorf("ensure branch exists: %w", err) } writer, err := repo.client.NewStagedWriter(ctx, ref) diff --git a/pkg/registry/apis/provisioning/repository/git/staged_test.go b/pkg/registry/apis/provisioning/repository/git/staged_test.go index 2b18fd3445f..8f3940f10f8 100644 --- a/pkg/registry/apis/provisioning/repository/git/staged_test.go +++ b/pkg/registry/apis/provisioning/repository/git/staged_test.go @@ -131,7 +131,48 @@ func TestNewStagedGitRepository(t *testing.T) { opts: repository.StageOptions{ Mode: repository.StageModeCommitOnEach, }, - wantError: errors.New("ref not found"), + wantError: errors.New("ensure branch exists: check branch exists: ref not found"), + }, + { + name: "creates branch when it doesn't exist", + setupMock: func(mockClient *mocks.FakeClient) { + // First call to GetRef for feature-branch returns not found + // Second call to GetRef for main branch (source) returns success + // Third call to CreateRef creates the feature branch + // Fourth call to GetRef for feature-branch returns the created branch + callCount := 0 + mockClient.GetRefStub = func(ctx context.Context, ref string) (nanogit.Ref, error) { + callCount++ + switch callCount { + case 1: + // First call: feature-branch doesn't exist + if ref == "refs/heads/feature-branch" { + return nanogit.Ref{}, nanogit.ErrObjectNotFound + } + case 2: + // Second call: get source branch (main) + if ref == "refs/heads/main" { + return nanogit.Ref{ + Name: "refs/heads/main", + Hash: hash.Hash{1, 2, 3}, + }, nil + } + } + return nanogit.Ref{}, errors.New("unexpected call") + } + + // CreateRef should be called to create the new branch + mockClient.CreateRefReturns(nil) + + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + }, + opts: repository.StageOptions{ + Ref: "feature-branch", + Mode: repository.StageModeCommitOnEach, + }, + expectedRef: "refs/heads/feature-branch", + wantError: nil, }, { name: "fails with NewStagedWriter error", diff --git a/pkg/registry/apis/provisioning/repository/github/github_repository_mock.go b/pkg/registry/apis/provisioning/repository/github/github_repository_mock.go index 7b5509935f1..e5fc32897cf 100644 --- a/pkg/registry/apis/provisioning/repository/github/github_repository_mock.go +++ b/pkg/registry/apis/provisioning/repository/github/github_repository_mock.go @@ -829,6 +829,65 @@ func (_c *MockGithubRepository_ReadTree_Call) RunAndReturn(run func(context.Cont return _c } +// RefURLs provides a mock function with given fields: ctx, ref +func (_m *MockGithubRepository) RefURLs(ctx context.Context, ref string) (*v0alpha1.RepositoryURLs, error) { + ret := _m.Called(ctx, ref) + + if len(ret) == 0 { + panic("no return value specified for RefURLs") + } + + var r0 *v0alpha1.RepositoryURLs + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*v0alpha1.RepositoryURLs, error)); ok { + return rf(ctx, ref) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *v0alpha1.RepositoryURLs); ok { + r0 = rf(ctx, ref) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0alpha1.RepositoryURLs) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, ref) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockGithubRepository_RefURLs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RefURLs' +type MockGithubRepository_RefURLs_Call struct { + *mock.Call +} + +// RefURLs is a helper method to define mock.On call +// - ctx context.Context +// - ref string +func (_e *MockGithubRepository_Expecter) RefURLs(ctx interface{}, ref interface{}) *MockGithubRepository_RefURLs_Call { + return &MockGithubRepository_RefURLs_Call{Call: _e.mock.On("RefURLs", ctx, ref)} +} + +func (_c *MockGithubRepository_RefURLs_Call) Run(run func(ctx context.Context, ref string)) *MockGithubRepository_RefURLs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockGithubRepository_RefURLs_Call) Return(_a0 *v0alpha1.RepositoryURLs, _a1 error) *MockGithubRepository_RefURLs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockGithubRepository_RefURLs_Call) RunAndReturn(run func(context.Context, string) (*v0alpha1.RepositoryURLs, error)) *MockGithubRepository_RefURLs_Call { + _c.Call.Return(run) + return _c +} + // Repo provides a mock function with no fields func (_m *MockGithubRepository) Repo() string { ret := _m.Called() @@ -875,23 +934,23 @@ func (_c *MockGithubRepository_Repo_Call) RunAndReturn(run func() string) *MockG } // ResourceURLs provides a mock function with given fields: ctx, file -func (_m *MockGithubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*v0alpha1.ResourceURLs, error) { +func (_m *MockGithubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*v0alpha1.RepositoryURLs, error) { ret := _m.Called(ctx, file) if len(ret) == 0 { panic("no return value specified for ResourceURLs") } - var r0 *v0alpha1.ResourceURLs + var r0 *v0alpha1.RepositoryURLs var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *repository.FileInfo) (*v0alpha1.ResourceURLs, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, *repository.FileInfo) (*v0alpha1.RepositoryURLs, error)); ok { return rf(ctx, file) } - if rf, ok := ret.Get(0).(func(context.Context, *repository.FileInfo) *v0alpha1.ResourceURLs); ok { + if rf, ok := ret.Get(0).(func(context.Context, *repository.FileInfo) *v0alpha1.RepositoryURLs); ok { r0 = rf(ctx, file) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*v0alpha1.ResourceURLs) + r0 = ret.Get(0).(*v0alpha1.RepositoryURLs) } } @@ -923,12 +982,12 @@ func (_c *MockGithubRepository_ResourceURLs_Call) Run(run func(ctx context.Conte return _c } -func (_c *MockGithubRepository_ResourceURLs_Call) Return(_a0 *v0alpha1.ResourceURLs, _a1 error) *MockGithubRepository_ResourceURLs_Call { +func (_c *MockGithubRepository_ResourceURLs_Call) Return(_a0 *v0alpha1.RepositoryURLs, _a1 error) *MockGithubRepository_ResourceURLs_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockGithubRepository_ResourceURLs_Call) RunAndReturn(run func(context.Context, *repository.FileInfo) (*v0alpha1.ResourceURLs, error)) *MockGithubRepository_ResourceURLs_Call { +func (_c *MockGithubRepository_ResourceURLs_Call) RunAndReturn(run func(context.Context, *repository.FileInfo) (*v0alpha1.RepositoryURLs, error)) *MockGithubRepository_ResourceURLs_Call { _c.Call.Return(run) return _c } diff --git a/pkg/registry/apis/provisioning/repository/github/repository.go b/pkg/registry/apis/provisioning/repository/github/repository.go index ac95e54312f..00ff70eb213 100644 --- a/pkg/registry/apis/provisioning/repository/github/repository.go +++ b/pkg/registry/apis/provisioning/repository/github/repository.go @@ -198,7 +198,7 @@ func (r *githubRepository) ListRefs(ctx context.Context) ([]provisioning.RefItem } // ResourceURLs implements RepositoryWithURLs. -func (r *githubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*provisioning.ResourceURLs, error) { +func (r *githubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*provisioning.RepositoryURLs, error) { cfg := r.config.Spec.GitHub if file.Path == "" || cfg == nil { return nil, nil @@ -209,7 +209,7 @@ func (r *githubRepository) ResourceURLs(ctx context.Context, file *repository.Fi ref = cfg.Branch } - urls := &provisioning.ResourceURLs{ + urls := &provisioning.RepositoryURLs{ RepositoryURL: cfg.URL, SourceURL: fmt.Sprintf("%s/blob/%s/%s", cfg.URL, ref, file.Path), } @@ -224,6 +224,25 @@ func (r *githubRepository) ResourceURLs(ctx context.Context, file *repository.Fi return urls, nil } +// RefURLs implements RepositoryWithURLs. +func (r *githubRepository) RefURLs(ctx context.Context, ref string) (*provisioning.RepositoryURLs, error) { + cfg := r.config.Spec.GitHub + if cfg == nil || ref == "" { + return nil, nil + } + + urls := &provisioning.RepositoryURLs{ + SourceURL: fmt.Sprintf("%s/tree/%s", cfg.URL, ref), + } + + if ref != cfg.Branch { + urls.CompareURL = fmt.Sprintf("%s/compare/%s...%s", cfg.URL, cfg.Branch, ref) + urls.NewPullRequestURL = fmt.Sprintf("%s?quick_pull=1&labels=grafana", urls.CompareURL) + } + + return urls, nil +} + func (r *githubRepository) OnCreate(_ context.Context) ([]map[string]interface{}, error) { return nil, nil } diff --git a/pkg/registry/apis/provisioning/repository/github/repository_test.go b/pkg/registry/apis/provisioning/repository/github/repository_test.go index 3a11127e9da..94d9c938cd5 100644 --- a/pkg/registry/apis/provisioning/repository/github/repository_test.go +++ b/pkg/registry/apis/provisioning/repository/github/repository_test.go @@ -688,7 +688,7 @@ func TestGitHubRepositoryResourceURLs(t *testing.T) { name string file *repository.FileInfo config *provisioning.Repository - expectedURLs *provisioning.ResourceURLs + expectedURLs *provisioning.RepositoryURLs expectedError error }{ { @@ -705,7 +705,7 @@ func TestGitHubRepositoryResourceURLs(t *testing.T) { }, }, }, - expectedURLs: &provisioning.ResourceURLs{ + expectedURLs: &provisioning.RepositoryURLs{ RepositoryURL: "https://github.com/grafana/grafana", SourceURL: "https://github.com/grafana/grafana/blob/feature-branch/dashboards/test.json", CompareURL: "https://github.com/grafana/grafana/compare/main...feature-branch", @@ -726,7 +726,7 @@ func TestGitHubRepositoryResourceURLs(t *testing.T) { }, }, }, - expectedURLs: &provisioning.ResourceURLs{ + expectedURLs: &provisioning.RepositoryURLs{ RepositoryURL: "https://github.com/grafana/grafana", SourceURL: "https://github.com/grafana/grafana/blob/main/dashboards/test.json", }, @@ -783,6 +783,92 @@ func TestGitHubRepositoryResourceURLs(t *testing.T) { } } +func TestGitHubRepositoryRefURLs(t *testing.T) { + tests := []struct { + name string + ref string + config *provisioning.Repository + expectedURLs *provisioning.RepositoryURLs + expectedError error + }{ + { + name: "ref different from branch", + ref: "feature-branch", + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/grafana/grafana", + Branch: "main", + }, + }, + }, + expectedURLs: &provisioning.RepositoryURLs{ + SourceURL: "https://github.com/grafana/grafana/tree/feature-branch", + CompareURL: "https://github.com/grafana/grafana/compare/main...feature-branch", + NewPullRequestURL: "https://github.com/grafana/grafana/compare/main...feature-branch?quick_pull=1&labels=grafana", + }, + }, + { + name: "ref same as branch", + ref: "main", + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/grafana/grafana", + Branch: "main", + }, + }, + }, + expectedURLs: &provisioning.RepositoryURLs{ + SourceURL: "https://github.com/grafana/grafana/tree/main", + }, + }, + { + name: "empty ref returns nil", + ref: "", + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/grafana/grafana", + Branch: "main", + }, + }, + }, + expectedURLs: nil, + }, + { + name: "nil github config returns nil", + ref: "feature-branch", + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: nil, + }, + }, + expectedURLs: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + repo := &githubRepository{ + config: tt.config, + owner: "grafana", + repo: "grafana", + } + + urls, err := repo.RefURLs(context.Background(), tt.ref) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedURLs, urls) + } + }) + } +} + // Test simple delegation functions func TestGitHubRepositoryDelegation(t *testing.T) { ctx := context.Background() diff --git a/pkg/registry/apis/provisioning/repository/reader_writer_mock.go b/pkg/registry/apis/provisioning/repository/reader_writer_mock.go new file mode 100644 index 00000000000..5990d5b3eb2 --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/reader_writer_mock.go @@ -0,0 +1,559 @@ +// Code generated by mockery v2.52.4. DO NOT EDIT. + +package repository + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + field "k8s.io/apimachinery/pkg/util/validation/field" + + v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +// MockReaderWriter is an autogenerated mock type for the ReaderWriter type +type MockReaderWriter struct { + mock.Mock +} + +type MockReaderWriter_Expecter struct { + mock *mock.Mock +} + +func (_m *MockReaderWriter) EXPECT() *MockReaderWriter_Expecter { + return &MockReaderWriter_Expecter{mock: &_m.Mock} +} + +// Config provides a mock function with no fields +func (_m *MockReaderWriter) Config() *v0alpha1.Repository { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Config") + } + + var r0 *v0alpha1.Repository + if rf, ok := ret.Get(0).(func() *v0alpha1.Repository); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0alpha1.Repository) + } + } + + return r0 +} + +// MockReaderWriter_Config_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Config' +type MockReaderWriter_Config_Call struct { + *mock.Call +} + +// Config is a helper method to define mock.On call +func (_e *MockReaderWriter_Expecter) Config() *MockReaderWriter_Config_Call { + return &MockReaderWriter_Config_Call{Call: _e.mock.On("Config")} +} + +func (_c *MockReaderWriter_Config_Call) Run(run func()) *MockReaderWriter_Config_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockReaderWriter_Config_Call) Return(_a0 *v0alpha1.Repository) *MockReaderWriter_Config_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockReaderWriter_Config_Call) RunAndReturn(run func() *v0alpha1.Repository) *MockReaderWriter_Config_Call { + _c.Call.Return(run) + return _c +} + +// Create provides a mock function with given fields: ctx, path, ref, data, message +func (_m *MockReaderWriter) Create(ctx context.Context, path string, ref string, data []byte, message string) error { + ret := _m.Called(ctx, path, ref, data, message) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok { + r0 = rf(ctx, path, ref, data, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockReaderWriter_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type MockReaderWriter_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - ctx context.Context +// - path string +// - ref string +// - data []byte +// - message string +func (_e *MockReaderWriter_Expecter) Create(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockReaderWriter_Create_Call { + return &MockReaderWriter_Create_Call{Call: _e.mock.On("Create", ctx, path, ref, data, message)} +} + +func (_c *MockReaderWriter_Create_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockReaderWriter_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string)) + }) + return _c +} + +func (_c *MockReaderWriter_Create_Call) Return(_a0 error) *MockReaderWriter_Create_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockReaderWriter_Create_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockReaderWriter_Create_Call { + _c.Call.Return(run) + return _c +} + +// Delete provides a mock function with given fields: ctx, path, ref, message +func (_m *MockReaderWriter) Delete(ctx context.Context, path string, ref string, message string) error { + ret := _m.Called(ctx, path, ref, message) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { + r0 = rf(ctx, path, ref, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockReaderWriter_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type MockReaderWriter_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +// - ctx context.Context +// - path string +// - ref string +// - message string +func (_e *MockReaderWriter_Expecter) Delete(ctx interface{}, path interface{}, ref interface{}, message interface{}) *MockReaderWriter_Delete_Call { + return &MockReaderWriter_Delete_Call{Call: _e.mock.On("Delete", ctx, path, ref, message)} +} + +func (_c *MockReaderWriter_Delete_Call) Run(run func(ctx context.Context, path string, ref string, message string)) *MockReaderWriter_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string)) + }) + return _c +} + +func (_c *MockReaderWriter_Delete_Call) Return(_a0 error) *MockReaderWriter_Delete_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockReaderWriter_Delete_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockReaderWriter_Delete_Call { + _c.Call.Return(run) + return _c +} + +// Move provides a mock function with given fields: ctx, oldPath, newPath, ref, message +func (_m *MockReaderWriter) Move(ctx context.Context, oldPath string, newPath string, ref string, message string) error { + ret := _m.Called(ctx, oldPath, newPath, ref, message) + + if len(ret) == 0 { + panic("no return value specified for Move") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok { + r0 = rf(ctx, oldPath, newPath, ref, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockReaderWriter_Move_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Move' +type MockReaderWriter_Move_Call struct { + *mock.Call +} + +// Move is a helper method to define mock.On call +// - ctx context.Context +// - oldPath string +// - newPath string +// - ref string +// - message string +func (_e *MockReaderWriter_Expecter) Move(ctx interface{}, oldPath interface{}, newPath interface{}, ref interface{}, message interface{}) *MockReaderWriter_Move_Call { + return &MockReaderWriter_Move_Call{Call: _e.mock.On("Move", ctx, oldPath, newPath, ref, message)} +} + +func (_c *MockReaderWriter_Move_Call) Run(run func(ctx context.Context, oldPath string, newPath string, ref string, message string)) *MockReaderWriter_Move_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 *MockReaderWriter_Move_Call) Return(_a0 error) *MockReaderWriter_Move_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockReaderWriter_Move_Call) RunAndReturn(run func(context.Context, string, string, string, string) error) *MockReaderWriter_Move_Call { + _c.Call.Return(run) + return _c +} + +// Read provides a mock function with given fields: ctx, path, ref +func (_m *MockReaderWriter) Read(ctx context.Context, path string, ref string) (*FileInfo, error) { + ret := _m.Called(ctx, path, ref) + + if len(ret) == 0 { + panic("no return value specified for Read") + } + + var r0 *FileInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*FileInfo, error)); ok { + return rf(ctx, path, ref) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) *FileInfo); ok { + r0 = rf(ctx, path, ref) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*FileInfo) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, path, ref) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockReaderWriter_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read' +type MockReaderWriter_Read_Call struct { + *mock.Call +} + +// Read is a helper method to define mock.On call +// - ctx context.Context +// - path string +// - ref string +func (_e *MockReaderWriter_Expecter) Read(ctx interface{}, path interface{}, ref interface{}) *MockReaderWriter_Read_Call { + return &MockReaderWriter_Read_Call{Call: _e.mock.On("Read", ctx, path, ref)} +} + +func (_c *MockReaderWriter_Read_Call) Run(run func(ctx context.Context, path string, ref string)) *MockReaderWriter_Read_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MockReaderWriter_Read_Call) Return(_a0 *FileInfo, _a1 error) *MockReaderWriter_Read_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockReaderWriter_Read_Call) RunAndReturn(run func(context.Context, string, string) (*FileInfo, error)) *MockReaderWriter_Read_Call { + _c.Call.Return(run) + return _c +} + +// ReadTree provides a mock function with given fields: ctx, ref +func (_m *MockReaderWriter) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) { + ret := _m.Called(ctx, ref) + + if len(ret) == 0 { + panic("no return value specified for ReadTree") + } + + var r0 []FileTreeEntry + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]FileTreeEntry, error)); ok { + return rf(ctx, ref) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []FileTreeEntry); ok { + r0 = rf(ctx, ref) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]FileTreeEntry) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, ref) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockReaderWriter_ReadTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadTree' +type MockReaderWriter_ReadTree_Call struct { + *mock.Call +} + +// ReadTree is a helper method to define mock.On call +// - ctx context.Context +// - ref string +func (_e *MockReaderWriter_Expecter) ReadTree(ctx interface{}, ref interface{}) *MockReaderWriter_ReadTree_Call { + return &MockReaderWriter_ReadTree_Call{Call: _e.mock.On("ReadTree", ctx, ref)} +} + +func (_c *MockReaderWriter_ReadTree_Call) Run(run func(ctx context.Context, ref string)) *MockReaderWriter_ReadTree_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockReaderWriter_ReadTree_Call) Return(_a0 []FileTreeEntry, _a1 error) *MockReaderWriter_ReadTree_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockReaderWriter_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]FileTreeEntry, error)) *MockReaderWriter_ReadTree_Call { + _c.Call.Return(run) + return _c +} + +// Test provides a mock function with given fields: ctx +func (_m *MockReaderWriter) Test(ctx context.Context) (*v0alpha1.TestResults, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Test") + } + + var r0 *v0alpha1.TestResults + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*v0alpha1.TestResults, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *v0alpha1.TestResults); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0alpha1.TestResults) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockReaderWriter_Test_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Test' +type MockReaderWriter_Test_Call struct { + *mock.Call +} + +// Test is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockReaderWriter_Expecter) Test(ctx interface{}) *MockReaderWriter_Test_Call { + return &MockReaderWriter_Test_Call{Call: _e.mock.On("Test", ctx)} +} + +func (_c *MockReaderWriter_Test_Call) Run(run func(ctx context.Context)) *MockReaderWriter_Test_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockReaderWriter_Test_Call) Return(_a0 *v0alpha1.TestResults, _a1 error) *MockReaderWriter_Test_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockReaderWriter_Test_Call) RunAndReturn(run func(context.Context) (*v0alpha1.TestResults, error)) *MockReaderWriter_Test_Call { + _c.Call.Return(run) + return _c +} + +// Update provides a mock function with given fields: ctx, path, ref, data, message +func (_m *MockReaderWriter) Update(ctx context.Context, path string, ref string, data []byte, message string) error { + ret := _m.Called(ctx, path, ref, data, message) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok { + r0 = rf(ctx, path, ref, data, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockReaderWriter_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' +type MockReaderWriter_Update_Call struct { + *mock.Call +} + +// Update is a helper method to define mock.On call +// - ctx context.Context +// - path string +// - ref string +// - data []byte +// - message string +func (_e *MockReaderWriter_Expecter) Update(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockReaderWriter_Update_Call { + return &MockReaderWriter_Update_Call{Call: _e.mock.On("Update", ctx, path, ref, data, message)} +} + +func (_c *MockReaderWriter_Update_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockReaderWriter_Update_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string)) + }) + return _c +} + +func (_c *MockReaderWriter_Update_Call) Return(_a0 error) *MockReaderWriter_Update_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockReaderWriter_Update_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockReaderWriter_Update_Call { + _c.Call.Return(run) + return _c +} + +// Validate provides a mock function with no fields +func (_m *MockReaderWriter) Validate() field.ErrorList { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Validate") + } + + var r0 field.ErrorList + if rf, ok := ret.Get(0).(func() field.ErrorList); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(field.ErrorList) + } + } + + return r0 +} + +// MockReaderWriter_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate' +type MockReaderWriter_Validate_Call struct { + *mock.Call +} + +// Validate is a helper method to define mock.On call +func (_e *MockReaderWriter_Expecter) Validate() *MockReaderWriter_Validate_Call { + return &MockReaderWriter_Validate_Call{Call: _e.mock.On("Validate")} +} + +func (_c *MockReaderWriter_Validate_Call) Run(run func()) *MockReaderWriter_Validate_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockReaderWriter_Validate_Call) Return(_a0 field.ErrorList) *MockReaderWriter_Validate_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockReaderWriter_Validate_Call) RunAndReturn(run func() field.ErrorList) *MockReaderWriter_Validate_Call { + _c.Call.Return(run) + return _c +} + +// Write provides a mock function with given fields: ctx, path, ref, data, message +func (_m *MockReaderWriter) Write(ctx context.Context, path string, ref string, data []byte, message string) error { + ret := _m.Called(ctx, path, ref, data, message) + + if len(ret) == 0 { + panic("no return value specified for Write") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok { + r0 = rf(ctx, path, ref, data, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockReaderWriter_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write' +type MockReaderWriter_Write_Call struct { + *mock.Call +} + +// Write is a helper method to define mock.On call +// - ctx context.Context +// - path string +// - ref string +// - data []byte +// - message string +func (_e *MockReaderWriter_Expecter) Write(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockReaderWriter_Write_Call { + return &MockReaderWriter_Write_Call{Call: _e.mock.On("Write", ctx, path, ref, data, message)} +} + +func (_c *MockReaderWriter_Write_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockReaderWriter_Write_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string)) + }) + return _c +} + +func (_c *MockReaderWriter_Write_Call) Return(_a0 error) *MockReaderWriter_Write_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockReaderWriter_Write_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockReaderWriter_Write_Call { + _c.Call.Return(run) + return _c +} + +// NewMockReaderWriter creates a new instance of MockReaderWriter. 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 NewMockReaderWriter(t interface { + mock.TestingT + Cleanup(func()) +}) *MockReaderWriter { + mock := &MockReaderWriter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/registry/apis/provisioning/repository/repository.go b/pkg/registry/apis/provisioning/repository/repository.go index 5608366060b..6ac356fc6c5 100644 --- a/pkg/registry/apis/provisioning/repository/repository.go +++ b/pkg/registry/apis/provisioning/repository/repository.go @@ -119,17 +119,19 @@ type Writer interface { Move(ctx context.Context, oldPath, newPath, ref, message string) error } +//go:generate mockery --name ReaderWriter --structname MockReaderWriter --inpackage --filename reader_writer_mock.go --with-expecter type ReaderWriter interface { Reader Writer } -// Hooks called after the repository has been created, updated or deleted +//go:generate mockery --name RepositoryWithURLs --structname MockRepositoryWithURLs --inpackage --filename repository_with_urls_mock.go --with-expecter type RepositoryWithURLs interface { Repository // Get resource URLs for a file inside a repository - ResourceURLs(ctx context.Context, file *FileInfo) (*provisioning.ResourceURLs, error) + ResourceURLs(ctx context.Context, file *FileInfo) (*provisioning.RepositoryURLs, error) + RefURLs(ctx context.Context, ref string) (*provisioning.RepositoryURLs, error) } // Hooks called after the repository has been created, updated or deleted diff --git a/pkg/registry/apis/provisioning/repository/repository_with_urls_mock.go b/pkg/registry/apis/provisioning/repository/repository_with_urls_mock.go new file mode 100644 index 00000000000..c277cb32026 --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/repository_with_urls_mock.go @@ -0,0 +1,309 @@ +// Code generated by mockery v2.52.4. DO NOT EDIT. + +package repository + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + field "k8s.io/apimachinery/pkg/util/validation/field" + + v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +// MockRepositoryWithURLs is an autogenerated mock type for the RepositoryWithURLs type +type MockRepositoryWithURLs struct { + mock.Mock +} + +type MockRepositoryWithURLs_Expecter struct { + mock *mock.Mock +} + +func (_m *MockRepositoryWithURLs) EXPECT() *MockRepositoryWithURLs_Expecter { + return &MockRepositoryWithURLs_Expecter{mock: &_m.Mock} +} + +// Config provides a mock function with no fields +func (_m *MockRepositoryWithURLs) Config() *v0alpha1.Repository { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Config") + } + + var r0 *v0alpha1.Repository + if rf, ok := ret.Get(0).(func() *v0alpha1.Repository); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0alpha1.Repository) + } + } + + return r0 +} + +// MockRepositoryWithURLs_Config_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Config' +type MockRepositoryWithURLs_Config_Call struct { + *mock.Call +} + +// Config is a helper method to define mock.On call +func (_e *MockRepositoryWithURLs_Expecter) Config() *MockRepositoryWithURLs_Config_Call { + return &MockRepositoryWithURLs_Config_Call{Call: _e.mock.On("Config")} +} + +func (_c *MockRepositoryWithURLs_Config_Call) Run(run func()) *MockRepositoryWithURLs_Config_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockRepositoryWithURLs_Config_Call) Return(_a0 *v0alpha1.Repository) *MockRepositoryWithURLs_Config_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockRepositoryWithURLs_Config_Call) RunAndReturn(run func() *v0alpha1.Repository) *MockRepositoryWithURLs_Config_Call { + _c.Call.Return(run) + return _c +} + +// RefURLs provides a mock function with given fields: ctx, ref +func (_m *MockRepositoryWithURLs) RefURLs(ctx context.Context, ref string) (*v0alpha1.RepositoryURLs, error) { + ret := _m.Called(ctx, ref) + + if len(ret) == 0 { + panic("no return value specified for RefURLs") + } + + var r0 *v0alpha1.RepositoryURLs + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*v0alpha1.RepositoryURLs, error)); ok { + return rf(ctx, ref) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *v0alpha1.RepositoryURLs); ok { + r0 = rf(ctx, ref) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0alpha1.RepositoryURLs) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, ref) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRepositoryWithURLs_RefURLs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RefURLs' +type MockRepositoryWithURLs_RefURLs_Call struct { + *mock.Call +} + +// RefURLs is a helper method to define mock.On call +// - ctx context.Context +// - ref string +func (_e *MockRepositoryWithURLs_Expecter) RefURLs(ctx interface{}, ref interface{}) *MockRepositoryWithURLs_RefURLs_Call { + return &MockRepositoryWithURLs_RefURLs_Call{Call: _e.mock.On("RefURLs", ctx, ref)} +} + +func (_c *MockRepositoryWithURLs_RefURLs_Call) Run(run func(ctx context.Context, ref string)) *MockRepositoryWithURLs_RefURLs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockRepositoryWithURLs_RefURLs_Call) Return(_a0 *v0alpha1.RepositoryURLs, _a1 error) *MockRepositoryWithURLs_RefURLs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRepositoryWithURLs_RefURLs_Call) RunAndReturn(run func(context.Context, string) (*v0alpha1.RepositoryURLs, error)) *MockRepositoryWithURLs_RefURLs_Call { + _c.Call.Return(run) + return _c +} + +// ResourceURLs provides a mock function with given fields: ctx, file +func (_m *MockRepositoryWithURLs) ResourceURLs(ctx context.Context, file *FileInfo) (*v0alpha1.RepositoryURLs, error) { + ret := _m.Called(ctx, file) + + if len(ret) == 0 { + panic("no return value specified for ResourceURLs") + } + + var r0 *v0alpha1.RepositoryURLs + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *FileInfo) (*v0alpha1.RepositoryURLs, error)); ok { + return rf(ctx, file) + } + if rf, ok := ret.Get(0).(func(context.Context, *FileInfo) *v0alpha1.RepositoryURLs); ok { + r0 = rf(ctx, file) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0alpha1.RepositoryURLs) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *FileInfo) error); ok { + r1 = rf(ctx, file) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRepositoryWithURLs_ResourceURLs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResourceURLs' +type MockRepositoryWithURLs_ResourceURLs_Call struct { + *mock.Call +} + +// ResourceURLs is a helper method to define mock.On call +// - ctx context.Context +// - file *FileInfo +func (_e *MockRepositoryWithURLs_Expecter) ResourceURLs(ctx interface{}, file interface{}) *MockRepositoryWithURLs_ResourceURLs_Call { + return &MockRepositoryWithURLs_ResourceURLs_Call{Call: _e.mock.On("ResourceURLs", ctx, file)} +} + +func (_c *MockRepositoryWithURLs_ResourceURLs_Call) Run(run func(ctx context.Context, file *FileInfo)) *MockRepositoryWithURLs_ResourceURLs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*FileInfo)) + }) + return _c +} + +func (_c *MockRepositoryWithURLs_ResourceURLs_Call) Return(_a0 *v0alpha1.RepositoryURLs, _a1 error) *MockRepositoryWithURLs_ResourceURLs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRepositoryWithURLs_ResourceURLs_Call) RunAndReturn(run func(context.Context, *FileInfo) (*v0alpha1.RepositoryURLs, error)) *MockRepositoryWithURLs_ResourceURLs_Call { + _c.Call.Return(run) + return _c +} + +// Test provides a mock function with given fields: ctx +func (_m *MockRepositoryWithURLs) Test(ctx context.Context) (*v0alpha1.TestResults, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Test") + } + + var r0 *v0alpha1.TestResults + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*v0alpha1.TestResults, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *v0alpha1.TestResults); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0alpha1.TestResults) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockRepositoryWithURLs_Test_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Test' +type MockRepositoryWithURLs_Test_Call struct { + *mock.Call +} + +// Test is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockRepositoryWithURLs_Expecter) Test(ctx interface{}) *MockRepositoryWithURLs_Test_Call { + return &MockRepositoryWithURLs_Test_Call{Call: _e.mock.On("Test", ctx)} +} + +func (_c *MockRepositoryWithURLs_Test_Call) Run(run func(ctx context.Context)) *MockRepositoryWithURLs_Test_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockRepositoryWithURLs_Test_Call) Return(_a0 *v0alpha1.TestResults, _a1 error) *MockRepositoryWithURLs_Test_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockRepositoryWithURLs_Test_Call) RunAndReturn(run func(context.Context) (*v0alpha1.TestResults, error)) *MockRepositoryWithURLs_Test_Call { + _c.Call.Return(run) + return _c +} + +// Validate provides a mock function with no fields +func (_m *MockRepositoryWithURLs) Validate() field.ErrorList { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Validate") + } + + var r0 field.ErrorList + if rf, ok := ret.Get(0).(func() field.ErrorList); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(field.ErrorList) + } + } + + return r0 +} + +// MockRepositoryWithURLs_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate' +type MockRepositoryWithURLs_Validate_Call struct { + *mock.Call +} + +// Validate is a helper method to define mock.On call +func (_e *MockRepositoryWithURLs_Expecter) Validate() *MockRepositoryWithURLs_Validate_Call { + return &MockRepositoryWithURLs_Validate_Call{Call: _e.mock.On("Validate")} +} + +func (_c *MockRepositoryWithURLs_Validate_Call) Run(run func()) *MockRepositoryWithURLs_Validate_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockRepositoryWithURLs_Validate_Call) Return(_a0 field.ErrorList) *MockRepositoryWithURLs_Validate_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockRepositoryWithURLs_Validate_Call) RunAndReturn(run func() field.ErrorList) *MockRepositoryWithURLs_Validate_Call { + _c.Call.Return(run) + return _c +} + +// NewMockRepositoryWithURLs creates a new instance of MockRepositoryWithURLs. 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 NewMockRepositoryWithURLs(t interface { + mock.TestingT + Cleanup(func()) +}) *MockRepositoryWithURLs { + mock := &MockRepositoryWithURLs{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/registry/apis/provisioning/resources/dualwriter.go b/pkg/registry/apis/provisioning/resources/dualwriter.go index 78042c35a12..bfe4ca499a6 100644 --- a/pkg/registry/apis/provisioning/resources/dualwriter.go +++ b/pkg/registry/apis/provisioning/resources/dualwriter.go @@ -576,7 +576,7 @@ func (r *DualReadWriter) deleteFolder(ctx context.Context, opts DualWriteOptions return folderDeleteResponse(ctx, opts.Path, opts.Ref, r.repo) } -func getFolderURLs(ctx context.Context, path, ref string, repo repository.Repository) (*provisioning.ResourceURLs, error) { +func getFolderURLs(ctx context.Context, path, ref string, repo repository.Repository) (*provisioning.RepositoryURLs, error) { if urlRepo, ok := repo.(repository.RepositoryWithURLs); ok && ref != "" { urls, err := urlRepo.ResourceURLs(ctx, &repository.FileInfo{Path: path, Ref: ref}) if err != nil { diff --git a/pkg/registry/apis/provisioning/resources/parser.go b/pkg/registry/apis/provisioning/resources/parser.go index 2b3f0c2deac..8c6c3b94587 100644 --- a/pkg/registry/apis/provisioning/resources/parser.go +++ b/pkg/registry/apis/provisioning/resources/parser.go @@ -87,7 +87,7 @@ type ParsedResource struct { Repo provisioning.ResourceRepositoryInfo // Resource URLs - URLs *provisioning.ResourceURLs + URLs *provisioning.RepositoryURLs // Check for classic file types (dashboard.json, etc) Classic provisioning.ClassicFileType diff --git a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json index b9df6bea5ea..dbe70573707 100644 --- a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json @@ -3135,6 +3135,14 @@ "items": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobResourceSummary" } + }, + "url": { + "description": "URLs contains URLs for the reference branch or commit if applicable.", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositoryURLs" + } + ] } } }, @@ -3516,6 +3524,27 @@ } } }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositoryURLs": { + "type": "object", + "properties": { + "compareURL": { + "description": "Compare this version to the target branch", + "type": "string" + }, + "newPullRequestURL": { + "description": "A URL that will create a new pull request for this branch", + "type": "string" + }, + "repositoryURL": { + "description": "A URL pointing to the repository this lives in", + "type": "string" + }, + "sourceURL": { + "description": "A URL pointing to the file or ref in the repository", + "type": "string" + } + } + }, "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceCount": { "type": "object", "required": [ @@ -3767,27 +3796,6 @@ } } }, - "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceURLs": { - "type": "object", - "properties": { - "compareURL": { - "description": "Compare this version to the target branch", - "type": "string" - }, - "newPullRequestURL": { - "description": "A URL that will create a new pull requeset for this branch", - "type": "string" - }, - "repositoryURL": { - "description": "A URL pointing to the repository this lives in", - "type": "string" - }, - "sourceURL": { - "description": "A URL pointing to the this file in the repository", - "type": "string" - } - } - }, "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceWrapper": { "description": "This is a container type for any resource type", "type": "object", @@ -3855,7 +3863,7 @@ "description": "Typed links for this file (only supported by external systems, github etc)", "allOf": [ { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceURLs" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositoryURLs" } ] } @@ -4666,6 +4674,9 @@ "description": "Summary of processed actions", "type": "array", "items": {} + }, + "url": { + "description": "URLs contains URLs for the reference branch or commit if applicable." } } }, @@ -4963,6 +4974,27 @@ } } }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryURLs": { + "type": "object", + "properties": { + "compareURL": { + "description": "Compare this version to the target branch", + "type": "string" + }, + "newPullRequestURL": { + "description": "A URL that will create a new pull request for this branch", + "type": "string" + }, + "repositoryURL": { + "description": "A URL pointing to the repository this lives in", + "type": "string" + }, + "sourceURL": { + "description": "A URL pointing to the file or ref in the repository", + "type": "string" + } + } + }, "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryView": { "type": "object", "required": [ @@ -5327,27 +5359,6 @@ } } }, - "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceURLs": { - "type": "object", - "properties": { - "compareURL": { - "description": "Compare this version to the target branch", - "type": "string" - }, - "newPullRequestURL": { - "description": "A URL that will create a new pull requeset for this branch", - "type": "string" - }, - "repositoryURL": { - "description": "A URL pointing to the repository this lives in", - "type": "string" - }, - "sourceURL": { - "description": "A URL pointing to the this file in the repository", - "type": "string" - } - } - }, "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceWrapper": { "description": "This is a container type for any resource type", "type": "object",