Provisioning: Include Ref URLs in Job Status (#109464)

* Add URLs to Job spec

* Rename them as RefURLs

* Implement RefURLs for Github

* Add Ref URLs to Jobs

* Worker Test

* Create the branch in the staged writer

* Regenerate Git mock

* Format code

* Consolidate ResourceURLs and RefURLs into one

* Fix broken tests
This commit is contained in:
Roberto Jiménez Sánchez
2025-08-12 11:57:37 +02:00
committed by GitHub
parent 90dfc26b43
commit f2303a8ad9
30 changed files with 2157 additions and 218 deletions
@@ -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
@@ -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
@@ -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 {
@@ -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"},
}
}
@@ -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
@@ -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
}
@@ -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
}
@@ -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"):
@@ -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")
@@ -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)
}
@@ -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
}
@@ -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)
}
@@ -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)
@@ -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")
@@ -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)
}
@@ -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 {
@@ -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)
}
@@ -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
}
@@ -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)
@@ -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)
@@ -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",
@@ -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
}
@@ -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
}
@@ -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()
@@ -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
}
@@ -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
@@ -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
}
@@ -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 {
@@ -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
@@ -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",