POC/Provisioning: Replace EditingOptions with a readOnly flag (#99859)

This commit is contained in:
Ryan McKinley
2025-01-31 10:07:33 +03:00
committed by GitHub
parent d52dd7d660
commit f5ef63bcec
16 changed files with 64 additions and 207 deletions
+10 -13
View File
@@ -3420,13 +3420,15 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "9"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "10"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "11"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "12"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "12"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "13"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "14"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "15"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "16"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "17"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "18"]
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "18"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "19"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "20"]
],
"public/app/features/dashboard-scene/saving/shared.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
@@ -5834,21 +5836,16 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "23"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "24"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "25"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "26"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "27"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "28"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "29"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "30"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "26"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "27"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "28"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "29"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "30"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "31"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "32"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "33"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "34"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "35"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "36"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "37"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "38"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "39"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "40"]
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "35"]
],
"public/app/features/provisioning/DeleteRepositoryButton.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
+2 -11
View File
@@ -74,8 +74,8 @@ type RepositorySpec struct {
// Repository description
Description string `json:"description,omitempty"`
// Edit options within the repository
Editing EditingOptions `json:"editing"`
// ReadOnly repository does not allow any write commands
ReadOnly bool `json:"readOnly"`
// Sync settings -- how values are pulled from the repository into grafana
Sync SyncOptions `json:"sync"`
@@ -130,15 +130,6 @@ type SyncOptions struct {
IntervalSeconds int64 `json:"intervalSeconds,omitempty"`
}
type EditingOptions struct {
// End users can create new files in the remote file system
Create bool `json:"create"`
// End users can update existing files in the remote file system
Update bool `json:"update"`
// End users can delete existing files in the remote file system
Delete bool `json:"delete"`
}
// The status of a Repository.
// This is expected never to be created by a kubectl call or similar, and is expected to rarely (if ever) be edited manually.
// As such, it is also a little less well structured than the spec, such as conditional-but-ever-present fields.
@@ -27,22 +27,6 @@ func (in *Author) DeepCopy() *Author {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EditingOptions) DeepCopyInto(out *EditingOptions) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EditingOptions.
func (in *EditingOptions) DeepCopy() *EditingOptions {
if in == nil {
return nil
}
out := new(EditingOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExportJobOptions) DeepCopyInto(out *ExportJobOptions) {
*out = *in
@@ -475,7 +459,6 @@ func (in *RepositoryList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RepositorySpec) DeepCopyInto(out *RepositorySpec) {
*out = *in
out.Editing = in.Editing
out.Sync = in.Sync
if in.Local != nil {
in, out := &in.Local, &out.Local
@@ -15,7 +15,6 @@ import (
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Author": schema_pkg_apis_provisioning_v0alpha1_Author(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.EditingOptions": schema_pkg_apis_provisioning_v0alpha1_EditingOptions(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ExportJobOptions": schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.FileItem": schema_pkg_apis_provisioning_v0alpha1_FileItem(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.FileList": schema_pkg_apis_provisioning_v0alpha1_FileList(ref),
@@ -86,43 +85,6 @@ func schema_pkg_apis_provisioning_v0alpha1_Author(ref common.ReferenceCallback)
}
}
func schema_pkg_apis_provisioning_v0alpha1_EditingOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"create": {
SchemaProps: spec.SchemaProps{
Description: "End users can create new files in the remote file system",
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
"update": {
SchemaProps: spec.SchemaProps{
Description: "End users can update existing files in the remote file system",
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
"delete": {
SchemaProps: spec.SchemaProps{
Description: "End users can delete existing files in the remote file system",
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
},
Required: []string{"create", "update", "delete"},
},
},
}
}
func schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -974,11 +936,12 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref common.ReferenceCa
Format: "",
},
},
"editing": {
"readOnly": {
SchemaProps: spec.SchemaProps{
Description: "Edit options within the repository",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.EditingOptions"),
Description: "ReadOnly repository does not allow any write commands",
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
"sync": {
@@ -1016,11 +979,11 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref common.ReferenceCa
},
},
},
Required: []string{"title", "editing", "sync", "type"},
Required: []string{"title", "readOnly", "sync", "type"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.EditingOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.GitHubRepositoryConfig", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.S3RepositoryConfig", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncOptions"},
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.GitHubRepositoryConfig", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.S3RepositoryConfig", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncOptions"},
}
}
@@ -1,43 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v0alpha1
// EditingOptionsApplyConfiguration represents a declarative configuration of the EditingOptions type for use
// with apply.
type EditingOptionsApplyConfiguration struct {
Create *bool `json:"create,omitempty"`
Update *bool `json:"update,omitempty"`
Delete *bool `json:"delete,omitempty"`
}
// EditingOptionsApplyConfiguration constructs a declarative configuration of the EditingOptions type for use with
// apply.
func EditingOptions() *EditingOptionsApplyConfiguration {
return &EditingOptionsApplyConfiguration{}
}
// WithCreate sets the Create 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 Create field is set to the value of the last call.
func (b *EditingOptionsApplyConfiguration) WithCreate(value bool) *EditingOptionsApplyConfiguration {
b.Create = &value
return b
}
// WithUpdate sets the Update 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 Update field is set to the value of the last call.
func (b *EditingOptionsApplyConfiguration) WithUpdate(value bool) *EditingOptionsApplyConfiguration {
b.Update = &value
return b
}
// WithDelete sets the Delete 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 Delete field is set to the value of the last call.
func (b *EditingOptionsApplyConfiguration) WithDelete(value bool) *EditingOptionsApplyConfiguration {
b.Delete = &value
return b
}
@@ -13,7 +13,7 @@ import (
type RepositorySpecApplyConfiguration struct {
Title *string `json:"title,omitempty"`
Description *string `json:"description,omitempty"`
Editing *EditingOptionsApplyConfiguration `json:"editing,omitempty"`
ReadOnly *bool `json:"readOnly,omitempty"`
Sync *SyncOptionsApplyConfiguration `json:"sync,omitempty"`
Type *provisioningv0alpha1.RepositoryType `json:"type,omitempty"`
Local *LocalRepositoryConfigApplyConfiguration `json:"local,omitempty"`
@@ -43,11 +43,11 @@ func (b *RepositorySpecApplyConfiguration) WithDescription(value string) *Reposi
return b
}
// WithEditing sets the Editing field in the declarative configuration to the given value
// WithReadOnly sets the ReadOnly 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 Editing field is set to the value of the last call.
func (b *RepositorySpecApplyConfiguration) WithEditing(value *EditingOptionsApplyConfiguration) *RepositorySpecApplyConfiguration {
b.Editing = value
// If called multiple times, the ReadOnly field is set to the value of the last call.
func (b *RepositorySpecApplyConfiguration) WithReadOnly(value bool) *RepositorySpecApplyConfiguration {
b.ReadOnly = &value
return b
}
@@ -20,8 +20,6 @@ import (
func ForKind(kind schema.GroupVersionKind) interface{} {
switch kind {
// Group=provisioning.grafana.app, Version=v0alpha1
case v0alpha1.SchemeGroupVersion.WithKind("EditingOptions"):
return &provisioningv0alpha1.EditingOptionsApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("GitHubRepositoryConfig"):
return &provisioningv0alpha1.GitHubRepositoryConfigApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("HealthStatus"):
+6
View File
@@ -52,6 +52,12 @@ func (c *exportConnector) Connect(
return nil, err
}
cfg := repo.Config()
if !cfg.Spec.ReadOnly {
return nil, &apierrors.StatusError{ErrStatus: v1.Status{
Code: http.StatusPreconditionFailed,
Message: "Repository is read only",
}}
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
options := &provisioning.ExportJobOptions{}
+6 -8
View File
@@ -183,11 +183,9 @@ func (s *filesConnector) doRead(ctx context.Context, repo repository.Repository,
}
func (s *filesConnector) doWrite(ctx context.Context, update bool, repo repository.Repository, path string, ref string, message string, req *http.Request) (*provisioning.ResourceWrapper, error) {
settings := repo.Config().Spec.Editing
if update && !settings.Update {
return nil, apierrors.NewForbidden(provisioning.RepositoryResourceInfo.GroupResource(), "updating files not enabled", nil)
} else if !settings.Create {
return nil, apierrors.NewForbidden(provisioning.RepositoryResourceInfo.GroupResource(), "creating files not enabled", nil)
if repo.Config().Spec.ReadOnly {
return nil, apierrors.NewForbidden(provisioning.RepositoryResourceInfo.GroupResource(),
"this repository is read only", nil)
}
defer func() { _ = req.Body.Close() }()
@@ -270,9 +268,9 @@ func (s *filesConnector) doWrite(ctx context.Context, update bool, repo reposito
}
func (s *filesConnector) doDelete(ctx context.Context, repo repository.Repository, path string, ref string, message string) (*provisioning.ResourceWrapper, error) {
settings := repo.Config().Spec.Editing
if !settings.Delete {
return nil, apierrors.NewForbidden(provisioning.RepositoryResourceInfo.GroupResource(), "deleting is not supported", nil)
if repo.Config().Spec.ReadOnly {
return nil, apierrors.NewForbidden(provisioning.RepositoryResourceInfo.GroupResource(),
"this repository is read only", nil)
}
err := repo.Delete(ctx, path, ref, message)
@@ -131,6 +131,13 @@ func (g *JobWorker) Process(ctx context.Context, job provisioning.Job, progress
}, nil
}
if repo.Config().Spec.ReadOnly {
return &provisioning.JobStatus{
State: provisioning.JobStateError,
Errors: []string{"Exporting to a read only repository is not supported"},
}, nil
}
var exporter Exporter
// Test for now... so we have something with long spinners for UI testing!!!
@@ -2653,31 +2653,6 @@
"additionalProperties": true,
"x-kubernetes-preserve-unknown-fields": true
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.EditingOptions": {
"type": "object",
"required": [
"create",
"update",
"delete"
],
"properties": {
"create": {
"description": "End users can create new files in the remote file system",
"type": "boolean",
"default": false
},
"delete": {
"description": "End users can delete existing files in the remote file system",
"type": "boolean",
"default": false
},
"update": {
"description": "End users can update existing files in the remote file system",
"type": "boolean",
"default": false
}
}
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ExportJobOptions": {
"type": "object",
"properties": {
@@ -3140,7 +3115,7 @@
"type": "object",
"required": [
"title",
"editing",
"readOnly",
"sync",
"type"
],
@@ -3149,15 +3124,6 @@
"description": "Repository description",
"type": "string"
},
"editing": {
"description": "Edit options within the repository",
"default": {},
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.EditingOptions"
}
]
},
"github": {
"description": "The repository on GitHub. Mutually exclusive with local | s3 | github.",
"allOf": [
@@ -3174,6 +3140,11 @@
}
]
},
"readOnly": {
"description": "ReadOnly repository does not allow any write commands",
"type": "boolean",
"default": false
},
"s3": {
"description": "The repository in an S3 bucket. Mutually exclusive with local | s3 | github.",
"allOf": [
@@ -111,6 +111,12 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop
return (
<form onSubmit={handleSubmit(doSave)}>
<Stack direction="column" gap={2}>
{repositoryConfig?.readOnly && (
<Alert title="This repository is read only">
If you have direct access to the target, copy the JSON and paste it there.
</Alert>
)}
{isNew && (
<>
<Field label={'Title'} invalid={!!errors.title} error={errors.title?.message}>
@@ -21,6 +21,8 @@ function generatePath(timestamp: number, pathFromAnnotation?: string, slug?: str
}
export function useDefaultValues({ meta, defaultTitle, defaultDescription }: UseDefaultValuesParams) {
// ???? why is this getting the repository from the folder, not the file???
// Should we only get it from the folder when new?
const folderRepository = useFolderRepository(meta.folderUid);
const timestamp = Date.now();
const annotations = meta.k8s?.annotations;
@@ -13,7 +13,6 @@ import {
Switch,
TextLink,
ControlledCollapse,
FieldSet,
RadioButtonGroup,
Stack,
} from '@grafana/ui';
@@ -46,11 +45,7 @@ function getDefaultValues(repository?: RepositorySpec): RepositoryFormData {
enabled: false,
target: 'instance',
},
editing: {
create: true,
delete: true,
update: true,
},
readOnly: false,
};
}
return specToData(repository);
@@ -216,17 +211,9 @@ export function ConfigForm({ data }: ConfigFormProps) {
<Field label={'Sync enabled'}>
<Switch {...register('sync.enabled')} id={'sync.enabled'} />
</Field>
<FieldSet label={'Editing options'}>
<Field label={'Create'} description={'Enable creating files on repository'}>
<Switch {...register('editing.create')} id={'editing.create'} />
</Field>
<Field label={'Update'} description={'Enable updating files on repository'}>
<Switch {...register('editing.update')} id={'editing.update'} />
</Field>
<Field label={'Delete'} description={'Enable deleting files on repository'}>
<Switch {...register('editing.delete')} id={'editing.delete'} />
</Field>
</FieldSet>
<Field label={'Read Only'} description={'Disable writing to this repository'}>
<Switch {...register('readOnly')} id={'readOnly'} />
</Field>
<Stack gap={2}>
<Button type={'submit'} disabled={request.isLoading}>
{request.isLoading ? 'Saving...' : 'Save'}
@@ -730,14 +730,6 @@ export type JobList = {
kind?: string;
metadata?: ListMeta;
};
export type EditingOptions = {
/** End users can create new files in the remote file system */
create: boolean;
/** End users can delete existing files in the remote file system */
delete: boolean;
/** End users can update existing files in the remote file system */
update: boolean;
};
export type GitHubRepositoryConfig = {
/** The branch to use in the repository. By default, this is the main branch. */
branch?: string;
@@ -774,12 +766,12 @@ export type SyncOptions = {
export type RepositorySpec = {
/** Repository description */
description?: string;
/** Edit options within the repository */
editing: EditingOptions;
/** The repository on GitHub. Mutually exclusive with local | s3 | github. */
github?: GitHubRepositoryConfig;
/** The repository on the local file system. Mutually exclusive with local | s3 | github. */
local?: LocalRepositoryConfig;
/** ReadOnly repository does not allow any write commands */
readOnly: boolean;
/** The repository in an S3 bucket. Mutually exclusive with local | s3 | github. */
s3?: S3RepositoryConfig;
/** Sync settings -- how values are pulled from the repository into grafana */
@@ -6,7 +6,7 @@ export const dataToSpec = (data: RepositoryFormData): RepositorySpec => {
type: data.type,
sync: data.sync,
title: data.title || '',
editing: data.editing,
readOnly: data.readOnly,
};
switch (data.type) {
case 'github':
@@ -40,7 +40,6 @@ export const specToData = (spec: RepositorySpec): RepositoryFormData => {
...spec,
owner: spec?.github?.owner || '',
repository: spec?.github?.repository || '',
editing: spec.editing,
...spec.github,
...spec.local,
...spec.s3,