Provisioning: Include URLs in ResourceWrapper response (#101511)

This commit is contained in:
Ryan McKinley authored and GitHub committed 2025-03-06 12:34:14 +03:00
1 parent 585fb5defe
commit de9febcab7
20 files changed
+435 -106

No files matched your search

+7 -7
View File
@@ -26,15 +26,15 @@ type JobList struct {
type JobAction string
const (
// Update a pull request -- send preview images, links etc
JobActionPullRequest JobAction = "pr"
// Sync the remote branch with the grafana instance
JobActionSync JobAction = "pull"
// Export from grafana into the remote repository
JobActionExport JobAction = "push"
// Process a pull request -- apply comments with preview images, links etc
JobActionPullRequest JobAction = "pr"
// Migration task -- this will migrate an full instance from SQL > Git
JobActionMigrate JobAction = "migrate"
)
@@ -69,11 +69,11 @@ type JobSpec struct {
// Pull request options
PullRequest *PullRequestJobOptions `json:"pr,omitempty"`
// Required when the action is `export`
Export *ExportJobOptions `json:"export,omitempty"`
// Required when the action is `push`
Push *ExportJobOptions `json:"push,omitempty"`
// Required when the action is `sync`
Sync *SyncJobOptions `json:"sync,omitempty"`
// Required when the action is `pull`
Pull *SyncJobOptions `json:"pull,omitempty"`
// Required when the action is `migrate`
Migrate *MigrateJobOptions `json:"migrate,omitempty"`
+35 -1
View File
@@ -219,12 +219,18 @@ type ResourceWrapper struct {
// Path to the remote file
Path string `json:"path,omitempty"`
// The commit hash (if exists)
// The request ref (or branch if exists)
Ref string `json:"ref,omitempty"`
// The repo hash value
Hash string `json:"hash,omitempty"`
// Basic repository info
Repository ResourceRepositoryInfo `json:"repository"`
// Typed links for this file (only supported by external systems, github etc)
URLs *ResourceURLs `json:"urls,omitempty"`
// The modified time in the remote file system
Timestamp *metav1.Time `json:"timestamp,omitempty"`
@@ -264,6 +270,34 @@ type ResourceObjects struct {
DryRun common.Unstructured `json:"dryRun,omitempty"`
}
type ResourceRepositoryInfo struct {
// The repository type
Type RepositoryType `json:"type"`
// The display name for this repository
Title string `json:"title"`
// The namespace this belongs to
Namespace string `json:"namespace"`
// The name (identifier)
Name string `json:"name"`
}
type ResourceURLs struct {
// A URL pointing to the this file in the repository
SourceURL string `json:"sourceURL,omitempty"`
// 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
NewPullRequestURL string `json:"newPullRequestURL,omitempty"`
// Compare this version to the target branch
CompareURL string `json:"compareURL,omitempty"`
}
// Information we can get just from the file listing
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type FileList struct {
@@ -276,13 +276,13 @@ func (in *JobSpec) DeepCopyInto(out *JobSpec) {
*out = new(PullRequestJobOptions)
**out = **in
}
if in.Export != nil {
in, out := &in.Export, &out.Export
if in.Push != nil {
in, out := &in.Push, &out.Push
*out = new(ExportJobOptions)
**out = **in
}
if in.Sync != nil {
in, out := &in.Sync, &out.Sync
if in.Pull != nil {
in, out := &in.Pull, &out.Pull
*out = new(SyncJobOptions)
**out = **in
}
@@ -634,6 +634,22 @@ func (in *ResourceObjects) DeepCopy() *ResourceObjects {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceRepositoryInfo) DeepCopyInto(out *ResourceRepositoryInfo) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRepositoryInfo.
func (in *ResourceRepositoryInfo) DeepCopy() *ResourceRepositoryInfo {
if in == nil {
return nil
}
out := new(ResourceRepositoryInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceStats) DeepCopyInto(out *ResourceStats) {
*out = *in
@@ -681,10 +697,32 @@ 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
out.TypeMeta = in.TypeMeta
out.Repository = in.Repository
if in.URLs != nil {
in, out := &in.URLs, &out.URLs
*out = new(ResourceURLs)
**out = **in
}
if in.Timestamp != nil {
in, out := &in.Timestamp, &out.Timestamp
*out = (*in).DeepCopy()
@@ -40,8 +40,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceList": schema_pkg_apis_provisioning_v0alpha1_ResourceList(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceListItem": schema_pkg_apis_provisioning_v0alpha1_ResourceListItem(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceObjects": schema_pkg_apis_provisioning_v0alpha1_ResourceObjects(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo": schema_pkg_apis_provisioning_v0alpha1_ResourceRepositoryInfo(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceStats": schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceType": schema_pkg_apis_provisioning_v0alpha1_ResourceType(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceURLs": schema_pkg_apis_provisioning_v0alpha1_ResourceURLs(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceWrapper": schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncJobOptions": schema_pkg_apis_provisioning_v0alpha1_SyncJobOptions(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncOptions": schema_pkg_apis_provisioning_v0alpha1_SyncOptions(ref),
@@ -618,11 +620,11 @@ func schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref common.ReferenceCallback)
Properties: map[string]spec.Schema{
"action": {
SchemaProps: spec.SchemaProps{
Description: "Possible enum values:\n - `\"export\"` Export from grafana into the remote repository\n - `\"migrate\"` Migration task -- this will migrate an full instance from SQL > Git\n - `\"pr\"` Update a pull request -- send preview images, links etc\n - `\"sync\"` Sync the remote branch with the grafana instance",
Description: "Possible enum values:\n - `\"migrate\"` Migration task -- this will migrate an full instance from SQL > Git\n - `\"pr\"` Process a pull request -- apply comments with preview images, links etc\n - `\"pull\"` Sync the remote branch with the grafana instance\n - `\"push\"` Export from grafana into the remote repository",
Default: "",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"export", "migrate", "pr", "sync"},
Enum: []interface{}{"migrate", "pr", "pull", "push"},
},
},
"repository": {
@@ -639,15 +641,15 @@ func schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref common.ReferenceCallback)
Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.PullRequestJobOptions"),
},
},
"export": {
"push": {
SchemaProps: spec.SchemaProps{
Description: "Required when the action is `export`",
Description: "Required when the action is `push`",
Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ExportJobOptions"),
},
},
"sync": {
"pull": {
SchemaProps: spec.SchemaProps{
Description: "Required when the action is `sync`",
Description: "Required when the action is `pull`",
Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncJobOptions"),
},
},
@@ -1387,6 +1389,52 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceObjects(ref common.ReferenceC
}
}
func schema_pkg_apis_provisioning_v0alpha1_ResourceRepositoryInfo(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"type": {
SchemaProps: spec.SchemaProps{
Description: "The repository type\n\nPossible enum values:\n - `\"github\"`\n - `\"local\"`",
Default: "",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"github", "local"},
},
},
"title": {
SchemaProps: spec.SchemaProps{
Description: "The display name for this repository",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"namespace": {
SchemaProps: spec.SchemaProps{
Description: "The namespace this belongs to",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"name": {
SchemaProps: spec.SchemaProps{
Description: "The name (identifier)",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"type", "title", "namespace", "name"},
},
},
}
}
func schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -1484,6 +1532,46 @@ 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{
@@ -1514,7 +1602,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceC
},
"ref": {
SchemaProps: spec.SchemaProps{
Description: "The commit hash (if exists)",
Description: "The request ref (or branch if exists)",
Type: []string{"string"},
Format: "",
},
@@ -1526,6 +1614,19 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceC
Format: "",
},
},
"repository": {
SchemaProps: spec.SchemaProps{
Description: "Basic repository info",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo"),
},
},
"urls": {
SchemaProps: spec.SchemaProps{
Description: "Typed links for this file (only supported by external systems, github etc)",
Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceURLs"),
},
},
"timestamp": {
SchemaProps: spec.SchemaProps{
Description: "The modified time in the remote file system",
@@ -1560,11 +1661,11 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceC
},
},
},
Required: []string{"resource"},
Required: []string{"repository", "resource"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceObjects", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceObjects", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceURLs", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
}
}
@@ -12,5 +12,6 @@ API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provis
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,ResourceWrapper,URLs
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
@@ -366,7 +366,7 @@ func (rc *RepositoryController) addSyncJob(ctx context.Context, obj *provisionin
Spec: provisioning.JobSpec{
Repository: obj.GetName(),
Action: provisioning.JobActionSync,
Sync: syncOptions,
Pull: syncOptions,
},
})
if err != nil {
+1 -1
View File
@@ -71,7 +71,7 @@ func (c *exportConnector) Connect(
Spec: provisioning.JobSpec{
Action: provisioning.JobActionExport,
Repository: cfg.Name,
Export: options,
Push: options,
},
})
if err != nil {
@@ -48,7 +48,7 @@ func (r *ExportWorker) IsSupported(ctx context.Context, job provisioning.Job) bo
// Process will start a job
func (r *ExportWorker) Process(ctx context.Context, repo repository.Repository, job provisioning.Job, progress jobs.JobProgressRecorder) error {
options := job.Spec.Export
options := job.Spec.Push
if options == nil {
return errors.New("missing export settings")
}
@@ -172,7 +172,7 @@ func (w *MigrationWorker) Process(ctx context.Context, repo repository.Repositor
} else {
err = w.exportWorker.Process(ctx, rw, provisioning.Job{
Spec: provisioning.JobSpec{
Export: &provisioning.ExportJobOptions{
Push: &provisioning.ExportJobOptions{
Identifier: options.Identifier,
},
},
@@ -210,7 +210,7 @@ func (w *MigrationWorker) Process(ctx context.Context, repo repository.Repositor
// Delegate the import to a sync (from the already checked out go-git repository!)
err = w.syncWorker.Process(ctx, rw, provisioning.Job{
Spec: provisioning.JobSpec{
Sync: &provisioning.SyncJobOptions{
Pull: &provisioning.SyncJobOptions{
Incremental: false,
},
},
+4 -4
View File
@@ -175,12 +175,12 @@ func (s *jobStore) Add(ctx context.Context, job *provisioning.Job) (*provisionin
if job.Spec.Action == "" {
return nil, apierrors.NewBadRequest("missing spec.action")
}
if job.Spec.Action == provisioning.JobActionExport && job.Spec.Export == nil {
return nil, apierrors.NewBadRequest("missing spec.export")
if job.Spec.Action == provisioning.JobActionExport && job.Spec.Push == nil {
return nil, apierrors.NewBadRequest("missing spec.push")
}
if job.Spec.Action == provisioning.JobActionSync && job.Spec.Sync == nil {
return nil, apierrors.NewBadRequest("missing spec.sync")
if job.Spec.Action == provisioning.JobActionSync && job.Spec.Pull == nil {
return nil, apierrors.NewBadRequest("missing spec.pull")
}
// Only for add
@@ -101,7 +101,7 @@ func (r *SyncWorker) Process(ctx context.Context, repo repository.Repository, jo
return fmt.Errorf("failed to create sync job: %w", err)
}
syncError := syncJob.run(ctx, *job.Spec.Sync)
syncError := syncJob.run(ctx, *job.Spec.Pull)
jobStatus := progress.Complete(ctx, syncError)
syncStatus = jobStatus.ToSyncStatus(job.Name)
@@ -38,11 +38,12 @@ type githubRepository struct {
}
var (
_ Repository = (*githubRepository)(nil)
_ Hooks = (*githubRepository)(nil)
_ Versioned = (*githubRepository)(nil)
_ Writer = (*githubRepository)(nil)
_ Reader = (*githubRepository)(nil)
_ Repository = (*githubRepository)(nil)
_ Hooks = (*githubRepository)(nil)
_ Versioned = (*githubRepository)(nil)
_ Writer = (*githubRepository)(nil)
_ Reader = (*githubRepository)(nil)
_ RepositoryWithURLs = (*githubRepository)(nil)
)
func NewGitHub(
@@ -561,7 +562,7 @@ func (r *githubRepository) parsePushEvent(event *github.PushEvent) (*provisionin
Job: &provisioning.JobSpec{
Repository: r.Config().GetName(),
Action: provisioning.JobActionSync,
Sync: &provisioning.SyncJobOptions{
Pull: &provisioning.SyncJobOptions{
Incremental: true,
},
},
@@ -709,6 +710,33 @@ func (r *githubRepository) CommentPullRequestFile(ctx context.Context, prNumber
return r.gh.CreatePullRequestFileComment(ctx, r.owner, r.repo, prNumber, fileComment)
}
// ResourceURLs implements RepositoryWithURLs.
func (r *githubRepository) ResourceURLs(ctx context.Context, file *FileInfo) (*provisioning.ResourceURLs, error) {
cfg := r.config.Spec.GitHub
if file.Path == "" || cfg == nil {
return nil, nil
}
ref := file.Ref
if ref == "" {
ref = cfg.Branch
}
urls := &provisioning.ResourceURLs{
RepositoryURL: cfg.URL,
SourceURL: fmt.Sprintf("%s/blob/%s/%s", cfg.URL, ref, file.Path),
}
if ref != cfg.Branch {
urls.CompareURL = fmt.Sprintf("%s/compare/%s...%s", cfg.URL, cfg.Branch, ref)
// Create a new pull request
urls.NewPullRequestURL = fmt.Sprintf("%s?quick_pull=1&labels=grafana", urls.CompareURL)
}
return urls, nil
}
func (r *githubRepository) createWebhook(ctx context.Context) (pgh.WebhookConfig, error) {
secret, err := uuid.NewRandom()
if err != nil {
@@ -84,7 +84,7 @@ func TestParseWebhooks(t *testing.T) {
Job: &provisioning.JobSpec{ // we want to always push a sync job
Repository: "unit-test-repo",
Action: provisioning.JobActionSync,
Sync: &provisioning.SyncJobOptions{
Pull: &provisioning.SyncJobOptions{
Incremental: true,
},
},
@@ -94,7 +94,7 @@ func TestParseWebhooks(t *testing.T) {
Job: &provisioning.JobSpec{
Repository: "unit-test-repo",
Action: provisioning.JobActionSync,
Sync: &provisioning.SyncJobOptions{
Pull: &provisioning.SyncJobOptions{
Incremental: true,
},
},
@@ -99,6 +99,14 @@ type ReaderWriter interface {
Writer
}
// Hooks called after the repository has been created, updated or deleted
type RepositoryWithURLs interface {
Repository
// Get resource URLs for a file inside a repository
ResourceURLs(ctx context.Context, file *FileInfo) (*provisioning.ResourceURLs, error)
}
// Hooks called after the repository has been created, updated or deleted
type Hooks interface {
Repository
@@ -8,7 +8,6 @@ import (
"testing"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
@@ -97,10 +96,8 @@ spec:
require.NoError(t, err)
parser := &Parser{
repo: &provisioning.Repository{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
repo: provisioning.ResourceRepositoryInfo{
Name: "test",
},
client: &DynamicClient{},
kinds: &StaticKindsLookup{},
@@ -35,8 +35,15 @@ func (f *ParserFactory) GetParser(ctx context.Context, repo repository.Reader) (
return nil, err
}
urls, _ := repo.(repository.RepositoryWithURLs)
parser := &Parser{
repo: config,
repo: provisioning.ResourceRepositoryInfo{
Type: config.Spec.Type,
Title: config.Spec.Title,
Namespace: config.Namespace,
Name: config.Name,
},
urls: urls,
client: client,
kinds: kinds,
}
@@ -46,7 +53,10 @@ func (f *ParserFactory) GetParser(ctx context.Context, repo repository.Reader) (
type Parser struct {
// The target repository
repo *provisioning.Repository
repo provisioning.ResourceRepositoryInfo
// for repositories that have URL support
urls repository.RepositoryWithURLs
// client helper (for this namespace?)
client *DynamicClient
@@ -57,6 +67,12 @@ type ParsedResource struct {
// Original file Info
Info *repository.FileInfo
// The repository details
Repo provisioning.ResourceRepositoryInfo
// Resource URLs
URLs *provisioning.ResourceURLs
// Check for classic file types (dashboard.json, etc)
Classic provisioning.ClassicFileType
@@ -94,6 +110,7 @@ func (r *Parser) Parse(ctx context.Context, info *repository.FileInfo, validate
logger := logging.FromContext(ctx).With("path", info.Path, "validate", validate)
parsed = &ParsedResource{
Info: info,
Repo: r.repo,
}
if ShouldIgnorePath(info.Path) && info.Path != "" {
@@ -110,7 +127,15 @@ func (r *Parser) Parse(ctx context.Context, info *repository.FileInfo, validate
}
}
// Remove the internal UID,version and id if they exist
if r.urls != nil {
parsed.URLs, err = r.urls.ResourceURLs(ctx, info)
if err != nil {
logger.Debug("failed to load resource URLs", "error", err)
return parsed, err
}
}
// Remove the internal dashboard UID,version and id if they exist
if parsed.GVK.Group == dashboard.GROUP && parsed.GVK.Kind == "Dashboard" {
unstructured.RemoveNestedField(parsed.Obj.Object, "spec", "uid")
unstructured.RemoveNestedField(parsed.Obj.Object, "spec", "version")
@@ -122,17 +147,16 @@ func (r *Parser) Parse(ctx context.Context, info *repository.FileInfo, validate
return nil, err
}
obj := parsed.Obj
cfg := r.repo
// Validate the namespace
if obj.GetNamespace() != "" && obj.GetNamespace() != cfg.GetNamespace() {
if obj.GetNamespace() != "" && obj.GetNamespace() != r.repo.Namespace {
parsed.Errors = append(parsed.Errors, ErrNamespaceMismatch)
}
obj.SetNamespace(cfg.GetNamespace())
obj.SetNamespace(r.repo.Namespace)
parsed.Meta.SetManagerProperties(utils.ManagerProperties{
Kind: utils.ManagerKindRepo,
Identity: cfg.Name,
Identity: r.repo.Name,
})
parsed.Meta.SetSourceProperties(utils.SourceProperties{
Path: info.Path, // joinPathWithRef(info.Path, info.Ref),
@@ -141,7 +165,7 @@ func (r *Parser) Parse(ctx context.Context, info *repository.FileInfo, validate
// Calculate name+folder from the file path
if info.Path != "" {
objName, folderName := NamesFromHashedRepoPath(cfg.Name, info.Path)
objName, folderName := NamesFromHashedRepoPath(r.repo.Name, info.Path)
parsed.Meta.SetFolder(folderName)
if obj.GetName() == "" {
obj.SetName(objName) // use the name saved in config
@@ -243,11 +267,13 @@ func (f *ParsedResource) AsResourceWrapper() *provisioning.ResourceWrapper {
res.DryRun = v0alpha1.Unstructured{Object: f.DryRunResponse.Object}
}
wrap := &provisioning.ResourceWrapper{
Path: info.Path,
Ref: info.Ref,
Hash: info.Hash,
Timestamp: info.Modified,
Resource: res,
Path: info.Path,
Ref: info.Ref,
Hash: info.Hash,
Repository: f.Repo,
URLs: f.URLs,
Timestamp: info.Modified,
Resource: res,
}
for _, err := range f.Errors {
wrap.Errors = append(wrap.Errors, err.Error())
+1 -1
View File
@@ -74,7 +74,7 @@ func (c *syncConnector) Connect(
Spec: provisioning.JobSpec{
Action: provisioning.JobActionSync,
Repository: cfg.Name,
Sync: &options,
Pull: &options,
},
})
if err != nil {
@@ -3171,22 +3171,14 @@
],
"properties": {
"action": {
"description": "Possible enum values:\n - `\"export\"` Export from grafana into the remote repository\n - `\"migrate\"` Migration task -- this will migrate an full instance from SQL \u003e Git\n - `\"pr\"` Update a pull request -- send preview images, links etc\n - `\"sync\"` Sync the remote branch with the grafana instance",
"description": "Possible enum values:\n - `\"migrate\"` Migration task -- this will migrate an full instance from SQL \u003e Git\n - `\"pr\"` Process a pull request -- apply comments with preview images, links etc\n - `\"pull\"` Sync the remote branch with the grafana instance\n - `\"push\"` Export from grafana into the remote repository",
"type": "string",
"default": "",
"enum": [
"export",
"migrate",
"pr",
"sync"
]
},
"export": {
"description": "Required when the action is `export`",
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ExportJobOptions"
}
"pull",
"push"
]
},
"migrate": {
@@ -3205,18 +3197,26 @@
}
]
},
"repository": {
"description": "The the repository reference (for now also in labels)",
"type": "string",
"default": ""
},
"sync": {
"description": "Required when the action is `sync`",
"pull": {
"description": "Required when the action is `pull`",
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.SyncJobOptions"
}
]
},
"push": {
"description": "Required when the action is `push`",
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ExportJobOptions"
}
]
},
"repository": {
"description": "The the repository reference (for now also in labels)",
"type": "string",
"default": ""
}
}
},
@@ -3776,6 +3776,41 @@
}
}
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceRepositoryInfo": {
"type": "object",
"required": [
"type",
"title",
"namespace",
"name"
],
"properties": {
"name": {
"description": "The name (identifier)",
"type": "string",
"default": ""
},
"namespace": {
"description": "The namespace this belongs to",
"type": "string",
"default": ""
},
"title": {
"description": "The display name for this repository",
"type": "string",
"default": ""
},
"type": {
"description": "The repository type\n\nPossible enum values:\n - `\"github\"`\n - `\"local\"`",
"type": "string",
"default": "",
"enum": [
"github",
"local"
]
}
}
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceStats": {
"description": "Information we can get just from the file listing",
"type": "object",
@@ -3831,10 +3866,32 @@
}
}
},
"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",
"required": [
"repository",
"resource"
],
"properties": {
@@ -3864,9 +3921,18 @@
"type": "string"
},
"ref": {
"description": "The commit hash (if exists)",
"description": "The request ref (or branch if exists)",
"type": "string"
},
"repository": {
"description": "Basic repository info",
"default": {},
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceRepositoryInfo"
}
]
},
"resource": {
"description": "Different flavors of the same object",
"default": {},
@@ -3883,6 +3949,14 @@
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time"
}
]
},
"urls": {
"description": "Typed links for this file (only supported by external systems, github etc)",
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceURLs"
}
]
}
},
"x-kubernetes-group-version-kind": [
@@ -1,6 +1,6 @@
import { Alert, Icon, Stack } from '@grafana/ui';
import { DashboardPageRouteSearchParams } from 'app/features/dashboard/containers/types';
import { useGetRepositoryFilesWithPathQuery, useGetRepositoryQuery } from 'app/features/provisioning/api';
import { useGetRepositoryFilesWithPathQuery } from 'app/features/provisioning/api';
import { usePullRequestParam } from 'app/features/provisioning/hooks';
import { DashboardRoutes } from 'app/types';
@@ -25,10 +25,6 @@ function DashboardPreviewBannerContent({ queryParams, slug, path }: DashboardPre
const prParam = usePullRequestParam();
const file = useGetRepositoryFilesWithPathQuery({ name: slug, path, ref: queryParams.ref });
// Required to create the new PR link
// TODO?? can the relevant links be in the file response???
const repo = useGetRepositoryQuery({ name: slug });
if (file.data?.errors) {
return (
<Alert title="Error loading dashboard" severity="error" style={{ flex: 0 }}>
@@ -59,10 +55,8 @@ function DashboardPreviewBannerContent({ queryParams, slug, path }: DashboardPre
}
// Check if this is a github link
const githubSpec = repo.data?.spec?.type === 'github' ? repo.data.spec.github : undefined;
if (queryParams.ref?.length && githubSpec) {
const url = `${githubSpec.url}/compare/${githubSpec.branch}...${queryParams.ref}?quick_pull=1&labels=grafana`;
const githubURL = file.data?.urls?.newPullRequestURL ?? file.data?.urls?.compareURL;
if (githubURL) {
return (
<Alert
{...commonAlertProps}
@@ -73,7 +67,7 @@ function DashboardPreviewBannerContent({ queryParams, slug, path }: DashboardPre
<Icon name="external-link-alt" />
</Stack>
}
onRemove={() => window.open(url, '_blank')}
onRemove={() => window.open(githubURL, '_blank')}
>
The value is <strong>not yet</strong> saved in the grafana database
</Alert>
@@ -738,16 +738,6 @@ export type ObjectMeta = {
Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */
uid?: string;
};
export type ExportJobOptions = {
/** Target branch for export (only git) */
branch?: string;
/** The source folder (or empty) to export */
folder?: string;
/** Include the identifier in the exported metadata */
identifier: boolean;
/** Prefix in target file system */
prefix?: string;
};
export type MigrateJobOptions = {
/** Preserve history (if possible) */
history?: boolean;
@@ -769,23 +759,33 @@ export type SyncJobOptions = {
/** Incremental synchronization for versioned repositories */
incremental: boolean;
};
export type ExportJobOptions = {
/** Target branch for export (only git) */
branch?: string;
/** The source folder (or empty) to export */
folder?: string;
/** Include the identifier in the exported metadata */
identifier: boolean;
/** Prefix in target file system */
prefix?: string;
};
export type JobSpec = {
/** Possible enum values:
- `"export"` Export from grafana into the remote repository
- `"migrate"` Migration task -- this will migrate an full instance from SQL > Git
- `"pr"` Update a pull request -- send preview images, links etc
- `"sync"` Sync the remote branch with the grafana instance */
action: 'export' | 'migrate' | 'pr' | 'sync';
/** Required when the action is `export` */
export?: ExportJobOptions;
- `"pr"` Process a pull request -- apply comments with preview images, links etc
- `"pull"` Sync the remote branch with the grafana instance
- `"push"` Export from grafana into the remote repository */
action: 'migrate' | 'pr' | 'pull' | 'push';
/** Required when the action is `migrate` */
migrate?: MigrateJobOptions;
/** Pull request options */
pr?: PullRequestJobOptions;
/** Required when the action is `pull` */
pull?: SyncJobOptions;
/** Required when the action is `push` */
push?: ExportJobOptions;
/** The the repository reference (for now also in labels) */
repository: string;
/** Required when the action is `sync` */
sync?: SyncJobOptions;
};
export type JobResourceSummary = {
create?: number;
@@ -1034,6 +1034,20 @@ export type DeleteOptions = {
/** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */
propagationPolicy?: string;
};
export type ResourceRepositoryInfo = {
/** The name (identifier) */
name: string;
/** The namespace this belongs to */
namespace: string;
/** The display name for this repository */
title: string;
/** The repository type
Possible enum values:
- `"github"`
- `"local"` */
type: 'github' | 'local';
};
export type Unstructured = {
[key: string]: any;
};
@@ -1068,6 +1082,16 @@ export type ResourceObjects = {
/** The identified type for this object */
type: ResourceType;
};
export type ResourceUrLs = {
/** Compare this version to the target branch */
compareURL?: string;
/** A URL that will create a new pull requeset for this branch */
newPullRequestURL?: string;
/** A URL pointing to the repository this lives in */
repositoryURL?: string;
/** A URL pointing to the this file in the repository */
sourceURL?: string;
};
export type ResourceWrapper = {
/** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */
apiVersion?: string;
@@ -1079,12 +1103,16 @@ export type ResourceWrapper = {
kind?: string;
/** Path to the remote file */
path?: string;
/** The commit hash (if exists) */
/** The request ref (or branch if exists) */
ref?: string;
/** Basic repository info */
repository: ResourceRepositoryInfo;
/** Different flavors of the same object */
resource: ResourceObjects;
/** The modified time in the remote file system */
timestamp?: Time;
/** Typed links for this file (only supported by external systems, github etc) */
urls?: ResourceUrLs;
};
export type ResourceListItem = {
folder?: string;