Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for raw folder permissions #1054

Merged
merged 1 commit into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/v1beta1/grafanafolder_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ type GrafanaFolderSpec struct {
// +optional
Title string `json:"title,omitempty"`

// raw json with folder permissions
// +optional
Permissions string `json:"permissions,omitempty"`

// selects Grafanas for import
InstanceSelector *metav1.LabelSelector `json:"instanceSelector"`

Expand Down Expand Up @@ -85,6 +89,7 @@ func (in *GrafanaFolderList) Find(namespace string, name string) *GrafanaFolder
func (in *GrafanaFolder) Hash() string {
hash := sha256.New()
hash.Write([]byte(in.Spec.Title))
hash.Write([]byte(in.Spec.Permissions))
return fmt.Sprintf("%x", hash.Sum(nil))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
permissions:
type: string
title:
type: string
required:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
permissions:
type: string
title:
type: string
required:
Expand Down
3 changes: 3 additions & 0 deletions config/grafana.integreatly.org_grafanafolders.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
permissions:
description: raw json with folder permissions
type: string
title:
type: string
required:
Expand Down
29 changes: 21 additions & 8 deletions controllers/grafanafolder_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package controllers

import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
Expand Down Expand Up @@ -301,14 +302,12 @@ func (r *GrafanaFolderReconciler) onFolderCreated(ctx context.Context, grafana *
}
}

if cr.Unchanged() && uid == remoteUID {
return nil
}

// Update title and replace uid if needed
err = grafanaClient.UpdateFolder(remoteUID, title, uid)
if err != nil {
return err
if !cr.Unchanged() || uid != remoteUID {
// Update title and replace uid if needed
err = grafanaClient.UpdateFolder(remoteUID, title, uid)
if err != nil {
return err
}
}
} else {
folderFromClient, err := grafanaClient.NewFolder(title, uid)
Expand All @@ -330,6 +329,20 @@ func (r *GrafanaFolderReconciler) onFolderCreated(ctx context.Context, grafana *
}
}

// NOTE: it's up to a user to reset permissions with correct json
if !cr.Unchanged() && cr.Spec.Permissions != "" {
permissions := grapi.PermissionItems{}
err = json.Unmarshal([]byte(cr.Spec.Permissions), &permissions)
if err != nil {
return fmt.Errorf("failed to unmarshal spec.permissions: %w", err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have an issue for dashboards around if we get an error for the specific dashboard it would be nice if we make this visible in the status as well.
Should we do that for folders also?
Might be a little painful since we have to consider multiple instances.
But for a json error like this it should be easy.

}

err = grafanaClient.UpdateFolderPermissions(uid, &permissions)
if err != nil {
return fmt.Errorf("failed to update folder permissions: %w", err)
}
}

return r.UpdateStatus(ctx, cr)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
permissions:
type: string
title:
type: string
required:
Expand Down
3 changes: 3 additions & 0 deletions deploy/kustomize/base/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,9 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
permissions:
description: raw json with folder permissions
type: string
title:
type: string
required:
Expand Down
7 changes: 7 additions & 0 deletions docs/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,13 @@ GrafanaFolderSpec defines the desired state of GrafanaFolder
allow to import this resources from an operator in a different namespace<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>permissions</b></td>
<td>string</td>
<td>
raw json with folder permissions<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>title</b></td>
<td>string</td>
Expand Down
47 changes: 47 additions & 0 deletions docs/docs/folder.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,51 @@ In a standard scenario, a folder with default settings gets created through a `G

If you need more control over folders (such as RBAC settings), it can be achieved through a `GrafanaFolder` CR.

**NOTE:** When the operator starts managing a folder, it changes the folder's uid to `metadata.uid` of the respective `GrafanaFolder` CR. There's no way to change that.

To view all configuration you can do within folders, look at our [API documentation](../api/#grafanafolderspec).

## Folder with custom title

```yaml
apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaFolder
metadata:
name: test-folder
spec:
instanceSelector:
matchLabels:
dashboards: "grafana"
# If title is not defined, the value will be taken from metadata.name
title: custom title
```

## Folder with custom permissions

When `permissions` value is empty/absent, a folder is created with default permissions. In all other scenarios, a raw JSON is passed to Grafana API, and it's up to Grafana to interpret it.

```yaml
apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaFolder
metadata:
name: test-folder
spec:
instanceSelector:
matchLabels:
dashboards: "grafana"
permissions: |
{
"items": [
{
"role": "Admin",
"permission": 4
},
{
"role": "Editor",
"permission": 2
}
]
}
```

**NOTE:** When an empty JSON is passed (`permissions: "{}"`), the access is stripped for everyone except for Admin (default Grafana behaviour).
8 changes: 8 additions & 0 deletions examples/folder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: "Folder with permissions"
linkTitle: "Folder with permissions"
---

Shows how to create a folder with custom title and [permissions](https://grafana.com/docs/grafana/v8.4/http_api/folder_permissions/#update-permissions-for-a-folder).

{{< readfile file="resources.yaml" code="true" lang="yaml" >}}
44 changes: 44 additions & 0 deletions examples/folder/resources.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
apiVersion: grafana.integreatly.org/v1beta1
kind: Grafana
metadata:
name: grafana
labels:
dashboards: "grafana"
spec:
config:
log:
mode: "console"
auth:
disable_login_form: "false"
security:
admin_user: root
admin_password: secret
---
apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaFolder
metadata:
name: test-folder
spec:
instanceSelector:
matchLabels:
dashboards: "grafana"

# If title is not defined, the value will be taken from metadata.name
title: custom title

# When permissions value is empty/absent, a folder is created with default permissions
# When empty JSON is passed ("{}"), the access is stripped for everyone except for Admin (default Grafana behaviour)
permissions: |
{
"items": [
{
"role": "Admin",
"permission": 4
},
{
"role": "Editor",
"permission": 2
}
]
}