diff --git a/pkg/apis/provisioning/v0alpha1/jobs.go b/pkg/apis/provisioning/v0alpha1/jobs.go index a97c1193a1c..f4f4df43410 100644 --- a/pkg/apis/provisioning/v0alpha1/jobs.go +++ b/pkg/apis/provisioning/v0alpha1/jobs.go @@ -63,6 +63,14 @@ type JobSpec struct { // The the repository reference (for now also in labels) Repository string `json:"repository"` + // Pull request options + PullRequest *PullRequestOptions `json:"pr,omitempty"` + + // Required when the action is `export` + Export *ExportOptions `json:"export,omitempty"` +} + +type PullRequestOptions struct { // The branch of commit hash Ref string `json:"ref,omitempty"` @@ -72,9 +80,6 @@ type JobSpec struct { // URL to the originator (eg, PR URL) URL string `json:"url,omitempty"` - - // Required when the action is `export` - Export *ExportOptions `json:"export,omitempty"` } type ExportOptions struct { @@ -101,6 +106,25 @@ type JobStatus struct { // Optional value 0-100 that can be set while running Progress float64 `json:"progress,omitempty"` + + // Summary of processed actions + Summary []JobResourceSummary `json:"summary,omitempty"` +} + +type JobResourceSummary struct { + Group string `json:"group,omitempty"` + Resource string `json:"resource,omitempty"` + + Create int64 `json:"create,omitempty"` + Update int64 `json:"update,omitempty"` + Delete int64 `json:"delete,omitempty"` + + // No action required (useful for sync) + Noop int64 `json:"noop,omitempty"` + + // Report errors for this resource type + // This may not be an exhaustive list and recommend looking at the logs for more info + Errors []string `json:"errors,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go index 4058522db60..7a0325736f2 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go @@ -283,9 +283,35 @@ func (in *JobList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JobResourceSummary) DeepCopyInto(out *JobResourceSummary) { + *out = *in + if in.Errors != nil { + in, out := &in.Errors, &out.Errors + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobResourceSummary. +func (in *JobResourceSummary) DeepCopy() *JobResourceSummary { + if in == nil { + return nil + } + out := new(JobResourceSummary) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JobSpec) DeepCopyInto(out *JobSpec) { *out = *in + if in.PullRequest != nil { + in, out := &in.PullRequest, &out.PullRequest + *out = new(PullRequestOptions) + **out = **in + } if in.Export != nil { in, out := &in.Export, &out.Export *out = new(ExportOptions) @@ -312,6 +338,13 @@ func (in *JobStatus) DeepCopyInto(out *JobStatus) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Summary != nil { + in, out := &in.Summary, &out.Summary + *out = make([]JobResourceSummary, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -357,6 +390,22 @@ func (in *LocalRepositoryConfig) DeepCopy() *LocalRepositoryConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PullRequestOptions) DeepCopyInto(out *PullRequestOptions) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PullRequestOptions. +func (in *PullRequestOptions) DeepCopy() *PullRequestOptions { + if in == nil { + return nil + } + out := new(PullRequestOptions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Repository) DeepCopyInto(out *Repository) { *out = *in diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index c228c7cf95a..b395e39614e 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -26,10 +26,12 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.HistoryList": schema_pkg_apis_provisioning_v0alpha1_HistoryList(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Job": schema_pkg_apis_provisioning_v0alpha1_Job(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobList": schema_pkg_apis_provisioning_v0alpha1_JobList(ref), + "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobResourceSummary": schema_pkg_apis_provisioning_v0alpha1_JobResourceSummary(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobSpec": schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobStatus": schema_pkg_apis_provisioning_v0alpha1_JobStatus(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.LintIssue": schema_pkg_apis_provisioning_v0alpha1_LintIssue(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_LocalRepositoryConfig(ref), + "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.PullRequestOptions": schema_pkg_apis_provisioning_v0alpha1_PullRequestOptions(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Repository": schema_pkg_apis_provisioning_v0alpha1_Repository(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositoryList": schema_pkg_apis_provisioning_v0alpha1_RepositoryList(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositorySpec": schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref), @@ -579,6 +581,70 @@ func schema_pkg_apis_provisioning_v0alpha1_JobList(ref common.ReferenceCallback) } } +func schema_pkg_apis_provisioning_v0alpha1_JobResourceSummary(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "create": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "update": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "delete": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "noop": { + SchemaProps: spec.SchemaProps{ + Description: "No action required (useful for sync)", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "errors": { + SchemaProps: spec.SchemaProps{ + Description: "Report errors for this resource type This may not be an exhaustive list and recommend looking at the logs for more info", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + func schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -602,31 +668,10 @@ func schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref common.ReferenceCallback) Format: "", }, }, - "ref": { - SchemaProps: spec.SchemaProps{ - Description: "The branch of commit hash", - Type: []string{"string"}, - Format: "", - }, - }, "pr": { SchemaProps: spec.SchemaProps{ - Description: "Pull request number (when appropriate)", - Type: []string{"integer"}, - Format: "int32", - }, - }, - "hash": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "url": { - SchemaProps: spec.SchemaProps{ - Description: "URL to the originator (eg, PR URL)", - Type: []string{"string"}, - Format: "", + Description: "Pull request options", + Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.PullRequestOptions"), }, }, "export": { @@ -640,7 +685,7 @@ func schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref common.ReferenceCallback) }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ExportOptions"}, + "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ExportOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.PullRequestOptions"}, } } @@ -698,9 +743,25 @@ func schema_pkg_apis_provisioning_v0alpha1_JobStatus(ref common.ReferenceCallbac Format: "double", }, }, + "summary": { + SchemaProps: spec.SchemaProps{ + Description: "Summary of processed actions", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobResourceSummary"), + }, + }, + }, + }, + }, }, }, }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobResourceSummary"}, } } @@ -758,6 +819,45 @@ func schema_pkg_apis_provisioning_v0alpha1_LocalRepositoryConfig(ref common.Refe } } +func schema_pkg_apis_provisioning_v0alpha1_PullRequestOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ref": { + SchemaProps: spec.SchemaProps{ + Description: "The branch of commit hash", + Type: []string{"string"}, + Format: "", + }, + }, + "pr": { + SchemaProps: spec.SchemaProps{ + Description: "Pull request number (when appropriate)", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "hash": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "url": { + SchemaProps: spec.SchemaProps{ + Description: "URL to the originator (eg, PR URL)", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_pkg_apis_provisioning_v0alpha1_Repository(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list index 85dcfb2f20b..0bb2061866b 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,12 +1,15 @@ API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,HealthStatus,Message API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,HistoryItem,Authors +API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobResourceSummary,Errors API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobStatus,Errors +API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobStatus,Summary API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositoryStatus,Stats API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ResourceWrapper,Errors API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ResourceWrapper,Lint API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,SyncStatus,Message API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,TestResults,Errors API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents +API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobSpec,PullRequest API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositorySpec,GitHub API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,SyncStatus,JobID API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,WebhookResponse,Message diff --git a/pkg/registry/apis/provisioning/jobs/exporter_dummy.go b/pkg/registry/apis/provisioning/jobs/exporter_dummy.go index 3af6ea1ea27..29298303195 100644 --- a/pkg/registry/apis/provisioning/jobs/exporter_dummy.go +++ b/pkg/registry/apis/provisioning/jobs/exporter_dummy.go @@ -23,6 +23,11 @@ func (s *dummyExporter) Export(ctx context.Context, logger.Info("start export", "folder", options.Folder) size := 5 + rand.IntN(15) + + dashboards := provisioning.JobResourceSummary{ + Group: "dashboard.grafana.app", + Resource: "dashboards", + } status := provisioning.JobStatus{ State: provisioning.JobStateWorking, Message: "exporting..." + repo.Config().Spec.Title, @@ -39,6 +44,23 @@ func (s *dummyExporter) Export(ctx context.Context, sleep := time.Duration(400+rand.IntN(800)) * time.Millisecond status.Message, _ = util.GetRandomString(rand.IntN(10 + 15)) time.Sleep(sleep) + + for j := 0; j < (4 + rand.IntN(10)); j++ { + switch rand.IntN(9) { + case 0, 1, 2: + dashboards.Create++ + case 3, 4: + dashboards.Update++ + case 5: + dashboards.Delete++ + case 6, 7, 8: + dashboards.Noop++ + } + } + status.Summary = []provisioning.JobResourceSummary{ + dashboards, + } + err = progress(status) if err != nil { return nil, err diff --git a/pkg/registry/apis/provisioning/jobs/pullrequest.go b/pkg/registry/apis/provisioning/jobs/pullrequest.go index d222b65e688..8c382ec53f5 100644 --- a/pkg/registry/apis/provisioning/jobs/pullrequest.go +++ b/pkg/registry/apis/provisioning/jobs/pullrequest.go @@ -15,6 +15,14 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" ) +type PullRequestWorker interface { + ProcessPullRequest(ctx context.Context, + repo repository.Repository, + options provisioning.PullRequestOptions, + progress func(provisioning.JobStatus) error, + ) (*provisioning.JobStatus, error) +} + // resourcePreview represents a resource that has changed in a pull request. type resourcePreview struct { Filename string @@ -65,7 +73,7 @@ type PreviewRenderer interface { RenderDashboardPreview(ctx context.Context, path string, ref string) (string, error) } -type PullRequestCommenter struct { +type pullRequestCommenter struct { repo PullRequestRepo parser *resources.Parser lintTemplate *template.Template @@ -79,7 +87,7 @@ func NewPullRequestCommenter( parser *resources.Parser, renderer PreviewRenderer, baseURL *url.URL, -) (*PullRequestCommenter, error) { +) (PullRequestWorker, error) { lintTemplate, err := template.New("comment").Parse(lintDashboardIssuesTemplate) if err != nil { return nil, fmt.Errorf("parse lint comment template: %w", err) @@ -89,7 +97,7 @@ func NewPullRequestCommenter( return nil, fmt.Errorf("parse previews comment template: %w", err) } - return &PullRequestCommenter{ + return &pullRequestCommenter{ repo: repo, parser: parser, lintTemplate: lintTemplate, @@ -99,40 +107,51 @@ func NewPullRequestCommenter( }, nil } -func (c *PullRequestCommenter) Process(ctx context.Context, job provisioning.Job) error { - if job.Spec.Action != provisioning.JobActionPullRequest { - return errors.New("job is not a pull request") - } +func (c *pullRequestCommenter) ProcessPullRequest(ctx context.Context, + repo repository.Repository, + options provisioning.PullRequestOptions, + progress func(provisioning.JobStatus) error, +) (*provisioning.JobStatus, error) { cfg := c.repo.Config().Spec // TODO: clean specification to have better options if !(cfg.Linting && cfg.GitHub.PullRequestLinter) && !cfg.GitHub.GenerateDashboardPreviews { - return nil + return &provisioning.JobStatus{ + State: provisioning.JobStateSuccess, + Message: "linting and previews are not required", + }, nil } - logger := logging.FromContext(ctx).With("pr", job.Spec.PR) + logger := logging.FromContext(ctx).With("pr", options.PR) logger.Info("process pull request") defer logger.Info("pull request processed") - spec := job.Spec base := cfg.GitHub.Branch - ref := spec.Hash + ref := options.Hash // list pull requests changes files files, err := c.repo.CompareFiles(ctx, base, ref) if err != nil { - return fmt.Errorf("failed to list pull request files: %w", err) + return &provisioning.JobStatus{ + State: provisioning.JobStateError, + Message: fmt.Sprintf("failed to list pull request files: %s", err.Error()), + }, nil } // clear all previous comments - if err := c.repo.ClearAllPullRequestFileComments(ctx, spec.PR); err != nil { - return fmt.Errorf("failed to clear pull request comments: %w", err) + if err := c.repo.ClearAllPullRequestFileComments(ctx, options.PR); err != nil { + return &provisioning.JobStatus{ + State: provisioning.JobStateError, + Message: fmt.Sprintf("failed to clear pull request comments: %+v", err), + }, nil } if len(files) == 0 { - logger.Info("no files to process") - return nil + return &provisioning.JobStatus{ + State: provisioning.JobStateSuccess, + Message: "no files to process", + }, nil } previews := make([]resourcePreview, 0, len(files)) @@ -145,7 +164,10 @@ func (c *PullRequestCommenter) Process(ctx context.Context, job provisioning.Job logger := logger.With("file", f.Path) fileInfo, err := c.repo.Read(ctx, f.Path, ref) if err != nil { - return fmt.Errorf("failed to read file %s: %w", f.Path, err) + return &provisioning.JobStatus{ + State: provisioning.JobStateError, + Message: fmt.Sprintf("failed to read file %s: %+v", f.Path, err), + }, nil } parsed, err := c.parser.Parse(ctx, fileInfo, true) @@ -161,11 +183,11 @@ func (c *PullRequestCommenter) Process(ctx context.Context, job provisioning.Job if cfg.Linting && cfg.GitHub.PullRequestLinter && len(parsed.Lint) > 0 && f.Action != repository.FileActionDeleted { var buf bytes.Buffer if err := c.lintTemplate.Execute(&buf, parsed.Lint); err != nil { - return fmt.Errorf("execute lint comment template: %w", err) + return nil, fmt.Errorf("execute lint comment template: %w", err) } - if err := c.repo.CommentPullRequestFile(ctx, spec.PR, f.Path, ref, buf.String()); err != nil { - return fmt.Errorf("comment pull request file %s: %w", f.Path, err) + if err := c.repo.CommentPullRequestFile(ctx, options.PR, f.Path, ref, buf.String()); err != nil { + return nil, fmt.Errorf("comment pull request file %s: %w", f.Path, err) } logger.Info("lint comment added") @@ -180,23 +202,23 @@ func (c *PullRequestCommenter) Process(ctx context.Context, job provisioning.Job switch f.Action { case repository.FileActionCreated: - preview.PreviewURL = c.previewURL(ref, f.Path, spec.URL) + preview.PreviewURL = c.previewURL(ref, f.Path, options.URL) case repository.FileActionUpdated: - preview.OriginalURL = c.previewURL(base, f.Path, spec.URL) - preview.PreviewURL = c.previewURL(ref, f.Path, spec.URL) + preview.OriginalURL = c.previewURL(base, f.Path, options.URL) + preview.PreviewURL = c.previewURL(ref, f.Path, options.URL) case repository.FileActionRenamed: - preview.OriginalURL = c.previewURL(base, f.PreviousPath, spec.URL) - preview.PreviewURL = c.previewURL(ref, f.Path, spec.URL) + preview.OriginalURL = c.previewURL(base, f.PreviousPath, options.URL) + preview.PreviewURL = c.previewURL(ref, f.Path, options.URL) case repository.FileActionDeleted: - preview.OriginalURL = c.previewURL(base, f.Path, spec.URL) + preview.OriginalURL = c.previewURL(base, f.Path, options.URL) default: - return fmt.Errorf("unknown file action: %s", f.Action) + return nil, fmt.Errorf("unknown file action: %s", f.Action) } if cfg.GitHub.GenerateDashboardPreviews && f.Action != repository.FileActionDeleted { screenshotURL, err := c.renderer.RenderDashboardPreview(ctx, f.Path, ref) if err != nil { - return fmt.Errorf("render dashboard preview: %w", err) + return nil, fmt.Errorf("render dashboard preview: %w", err) } preview.PreviewScreenshotURL = screenshotURL @@ -209,21 +231,24 @@ func (c *PullRequestCommenter) Process(ctx context.Context, job provisioning.Job if len(previews) > 0 && cfg.GitHub.GenerateDashboardPreviews { var buf bytes.Buffer if err := c.prevTemplate.Execute(&buf, previews); err != nil { - return fmt.Errorf("execute previews comment template: %w", err) + return nil, fmt.Errorf("execute previews comment template: %w", err) } - if err := c.repo.CommentPullRequest(ctx, spec.PR, buf.String()); err != nil { - return fmt.Errorf("comment pull request: %w", err) + if err := c.repo.CommentPullRequest(ctx, options.PR, buf.String()); err != nil { + return nil, fmt.Errorf("comment pull request: %w", err) } logger.Info("previews comment added", "number", len(previews)) } - return nil + return &provisioning.JobStatus{ + State: provisioning.JobStateSuccess, + Message: "finished", // can update with useful feedback + }, nil } // previewURL returns the URL to preview the file in Grafana -func (c *PullRequestCommenter) previewURL(ref, filePath, pullRequestURL string) string { +func (c *pullRequestCommenter) previewURL(ref, filePath, pullRequestURL string) string { // Copy the baseURL to modify path and query baseURL := *c.baseURL baseURL = *baseURL.JoinPath("/admin/provisioning", c.repo.Config().GetName(), "dashboard/preview", filePath) diff --git a/pkg/registry/apis/provisioning/jobs/worker.go b/pkg/registry/apis/provisioning/jobs/worker.go index 41f578ed6fe..9023f41b07d 100644 --- a/pkg/registry/apis/provisioning/jobs/worker.go +++ b/pkg/registry/apis/provisioning/jobs/worker.go @@ -6,6 +6,7 @@ import ( "net/url" "time" + apierrors "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/endpoints/request" @@ -159,14 +160,17 @@ func (g *JobWorker) Process(ctx context.Context, job provisioning.Job, progress id: id, } + options := job.Spec.PullRequest + if options == nil { + return nil, apierrors.NewBadRequest("missing spec.pr") + } + commenter, err := NewPullRequestCommenter(prRepo, parser, renderer, baseURL) if err != nil { return nil, fmt.Errorf("error creating pull request commenter: %w", err) } + return commenter.ProcessPullRequest(ctx, repo, *options, progress) - if err := commenter.Process(ctx, job); err != nil { - return nil, fmt.Errorf("error processing pull request: %w", err) - } case provisioning.JobActionExport: if job.Spec.Export == nil { return &provisioning.JobStatus{ diff --git a/pkg/registry/apis/provisioning/repository/github.go b/pkg/registry/apis/provisioning/repository/github.go index bbd6c398481..997972645a0 100644 --- a/pkg/registry/apis/provisioning/repository/github.go +++ b/pkg/registry/apis/provisioning/repository/github.go @@ -522,10 +522,12 @@ func (r *githubRepository) parsePullRequestEvent(event *github.PullRequestEvent) Job: &provisioning.JobSpec{ Repository: r.Config().GetName(), Action: provisioning.JobActionPullRequest, - URL: pr.GetHTMLURL(), - PR: pr.GetNumber(), - Ref: pr.GetHead().GetRef(), - Hash: pr.GetHead().GetSHA(), + PullRequest: &provisioning.PullRequestOptions{ + URL: pr.GetHTMLURL(), + PR: pr.GetNumber(), + Ref: pr.GetHead().GetRef(), + Hash: pr.GetHead().GetSHA(), + }, }, }, nil } diff --git a/pkg/registry/apis/provisioning/repository/github_test.go b/pkg/registry/apis/provisioning/repository/github_test.go index d44dc15fce9..1409235b4fa 100644 --- a/pkg/registry/apis/provisioning/repository/github_test.go +++ b/pkg/registry/apis/provisioning/repository/github_test.go @@ -68,10 +68,12 @@ func TestParseWebhooks(t *testing.T) { Job: &provisioning.JobSpec{ Repository: "unit-test-repo", Action: provisioning.JobActionPullRequest, - Ref: "dashboard/1733653266690", - Hash: "ab5446a53df9e5f8bdeed52250f51fad08e822bc", - PR: 12, - URL: "https://github.com/grafana/git-ui-sync-demo/pull/12", + PullRequest: &provisioning.PullRequestOptions{ + Ref: "dashboard/1733653266690", + Hash: "ab5446a53df9e5f8bdeed52250f51fad08e822bc", + PR: 12, + URL: "https://github.com/grafana/git-ui-sync-demo/pull/12", + }, }, }}, {"push", "different_branch", provisioning.WebhookResponse{ diff --git a/pkg/tests/apis/provisioning/provisioning_test.go b/pkg/tests/apis/provisioning/provisioning_test.go index 065461215c8..18591080609 100644 --- a/pkg/tests/apis/provisioning/provisioning_test.go +++ b/pkg/tests/apis/provisioning/provisioning_test.go @@ -43,7 +43,7 @@ func TestIntegrationProvisioning(t *testing.T) { AppModeProduction: false, // required for experimental APIs EnableFeatureToggles: []string{ featuremgmt.FlagProvisioning, - featuremgmt.FlagKubernetesFolders, // Required for tests that deal with folders. + featuremgmt.FlagKubernetesFoldersServiceV2, // Required for tests that deal with folders. featuremgmt.FlagUnifiedStorageSearch, }, PermittedProvisioningPaths: ".|" + provisioningPath, diff --git a/pkg/tests/apis/provisioning/testdata/openapi.json b/pkg/tests/apis/provisioning/testdata/openapi.json index d3615dd551d..6a83a3a36ac 100644 --- a/pkg/tests/apis/provisioning/testdata/openapi.json +++ b/pkg/tests/apis/provisioning/testdata/openapi.json @@ -3782,10 +3782,47 @@ } ] }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobResourceSummary": { + "type": "object", + "properties": { + "create": { + "type": "integer", + "format": "int64" + }, + "delete": { + "type": "integer", + "format": "int64" + }, + "errors": { + "description": "Report errors for this resource type This may not be an exhaustive list and recommend looking at the logs for more info", + "type": "array", + "items": { + "type": "string", + "default": "" + } + }, + "group": { + "type": "string" + }, + "noop": { + "description": "No action required (useful for sync)", + "type": "integer", + "format": "int64" + }, + "resource": { + "type": "string" + }, + "update": { + "type": "integer", + "format": "int64" + } + } + }, "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobSpec": { "type": "object", "required": [ - "action" + "action", + "repository" ], "properties": { "action": { @@ -3806,21 +3843,18 @@ } ] }, - "hash": { - "type": "string" - }, "pr": { - "description": "Pull request number (when appropriate)", - "type": "integer", - "format": "int32" + "description": "Pull request options", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.PullRequestOptions" + } + ] }, - "ref": { - "description": "The branch of commit hash", - "type": "string" - }, - "url": { - "description": "URL to the originator (eg, PR URL)", - "type": "string" + "repository": { + "description": "The the repository reference (for now also in labels)", + "type": "string", + "default": "" } } }, @@ -3843,7 +3877,7 @@ "type": "string" }, "progress": { - "description": "Optional value 0-1 that can be set while running", + "description": "Optional value 0-100 that can be set while running", "type": "number", "format": "double" }, @@ -3860,6 +3894,18 @@ "success", "working" ] + }, + "summary": { + "description": "Summary of processed actions", + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobResourceSummary" + } + ] + } } } }, @@ -3901,6 +3947,27 @@ } } }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.PullRequestOptions": { + "type": "object", + "properties": { + "hash": { + "type": "string" + }, + "pr": { + "description": "Pull request number (when appropriate)", + "type": "integer", + "format": "int32" + }, + "ref": { + "description": "The branch of commit hash", + "type": "string" + }, + "url": { + "description": "URL to the originator (eg, PR URL)", + "type": "string" + } + } + }, "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository": { "description": "When this code is changed, make sure to update the code generation. As of writing, this can be done via the hack dir in the root of the repo: ./hack/update-codegen.sh provisioning If you've opened the generated files in this dir at some point in VSCode, you may also have to re-open them to clear errors.", "type": "object", diff --git a/public/app/features/provisioning/api/endpoints.ts b/public/app/features/provisioning/api/endpoints.ts index a6c77a6226b..5887837a4ea 100644 --- a/public/app/features/provisioning/api/endpoints.ts +++ b/public/app/features/provisioning/api/endpoints.ts @@ -435,14 +435,27 @@ export type ExportOptions = { history?: boolean; prefix?: string; }; -export type JobSpec = { - action: 'export' | 'pr' | 'sync'; - export?: ExportOptions; +export type PullRequestOptions = { hash?: string; pr?: number; ref?: string; url?: string; }; +export type JobSpec = { + action: 'export' | 'pr' | 'sync'; + export?: ExportOptions; + pr?: PullRequestOptions; + repository: string; +}; +export type JobResourceSummary = { + create?: number; + delete?: number; + errors?: string[]; + group?: string; + noop?: number; + resource?: string; + update?: number; +}; export type JobStatus = { errors?: string[]; finished?: number; @@ -450,6 +463,7 @@ export type JobStatus = { progress?: number; started?: number; state?: 'error' | 'pending' | 'success' | 'working'; + summary?: JobResourceSummary[]; }; export type Job = { apiVersion?: string;