Skip to content

Commit

Permalink
Folder permissions (#821)
Browse files Browse the repository at this point in the history
* support folder-permissions

* address linter-issues

* more linter fixes

* bugfix: update manifest / fixed another linter-issue

* bugfix: fixed another manifest

* Set rbad for grafanafolders

* added/improved documentation and some code-cleanup

* fix linter-issues

* fixed missing api- and manifest-changes for new description

* fixed latest manifest and md linter issue

Co-authored-by: Peter Braun <pbraun@redhat.com>
Co-authored-by: Hubert Stefanski <35736504+HubertStefanski@users.noreply.github.com>
Co-authored-by: Edvin N <edvin.norling@xenit.se>
  • Loading branch information
4 people committed Sep 9, 2022
1 parent b7f9dc5 commit 3884b46
Show file tree
Hide file tree
Showing 24 changed files with 1,440 additions and 2 deletions.
94 changes: 94 additions & 0 deletions api/integreatly/v1alpha1/grafanafolder_types.go
@@ -0,0 +1,94 @@
/*
Copyright 2021.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"crypto/sha256"
"fmt"
"io"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type GrafanaPermissionItem struct {
PermissionTargetType string `json:"permissionTargetType"`
PermissionTarget string `json:"permissionTarget"`
PermissionLevel int `json:"permissionLevel"`
}

type GrafanaFolderSpec struct {
// FolderName is the display-name of the folder and must match CustomFolderName of any GrafanaDashboard you want to put in
FolderName string `json:"title"`

// FolderPermissions shall contain the _complete_ permissions for the folder.
// Any permission not listed here, will be removed from the folder.
FolderPermissions []GrafanaPermissionItem `json:"permissions,omitempty"`
}

// GrafanaFolder is the Schema for the grafana folders and folderpermissions API
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true
type GrafanaFolder struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec GrafanaFolderSpec `json:"spec,omitempty"`
}

// GrafanaFolderList contains a list of GrafanaFolder
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true
type GrafanaFolderList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []GrafanaFolder `json:"items"`
}

// GrafanaFolderRef is used to keep a folder reference without having access to the folder-struct itself
type GrafanaFolderRef struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Hash string `json:"hash"`
}

func init() {
SchemeBuilder.Register(&GrafanaFolder{}, &GrafanaFolderList{})
}

func (f *GrafanaFolder) Hash() string {
hash := sha256.New()

io.WriteString(hash, f.Spec.FolderName) // nolint
io.WriteString(hash, f.Namespace) // nolint

for _, p := range f.Spec.FolderPermissions {
io.WriteString(hash, p.PermissionTarget) // nolint
io.WriteString(hash, p.PermissionTargetType) // nolint
io.WriteString(hash, fmt.Sprint(p.PermissionLevel)) // nolint
}

return fmt.Sprintf("%x", hash.Sum(nil))
}

func (f *GrafanaFolder) GetPermissions() []*GrafanaPermissionItem {
var permissions = make([]*GrafanaPermissionItem, 0, len(f.Spec.FolderPermissions))
for _, p := range f.Spec.FolderPermissions {
var p2 = p // ensure allocated memory for current item
permissions = append(permissions, &p2)
}

return permissions
}
56 changes: 56 additions & 0 deletions api/integreatly/v1alpha1/grafanafolder_types_test.go
@@ -0,0 +1,56 @@
/*
Copyright 2021.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"github.com/stretchr/testify/require"
"testing"
)

var folderPermissions = []GrafanaPermissionItem{
{
PermissionTargetType: "role",
PermissionTarget: "Viewer",
PermissionLevel: 1,
},
{
PermissionTargetType: "role",
PermissionTarget: "Editor",
PermissionLevel: 2,
},
}

func TestGetPermissions(t *testing.T) {
folder := new(GrafanaFolder)
folder.Spec.FolderName = "TEST"
folder.Spec.FolderPermissions = folderPermissions

result := folder.GetPermissions()
require.NotNil(t, result)
require.Equal(t, 2, len(result))
require.Equal(t, "Viewer", result[0].PermissionTarget)
require.Equal(t, "Editor", result[1].PermissionTarget)
}

func TestHash(t *testing.T) {
folder := new(GrafanaFolder)
folder.Spec.FolderName = "TEST"
folder.Spec.FolderPermissions = folderPermissions

result := folder.Hash()
require.Equal(t, "c44659960c5741f3ee2f949e3df5a41c04a03acac15dbeb05f6c2a7423232b6c", result)
}
25 changes: 25 additions & 0 deletions api/integreatly/v1alpha1/selectors.go
Expand Up @@ -46,6 +46,31 @@ func (d *GrafanaDashboard) MatchesSelectors(s []*metav1.LabelSelector) (bool, er
return result, nil
}

func (d *GrafanaFolder) matchesSelector(s *metav1.LabelSelector) (bool, error) {
selector, err := metav1.LabelSelectorAsSelector(s)
if err != nil {
return false, err
}

return selector.Empty() || selector.Matches(labels.Set(d.Labels)), nil
}

// Check if the dashboard-folder matches at least one of the selectors
func (d *GrafanaFolder) MatchesSelectors(s []*metav1.LabelSelector) (bool, error) {
result := false

for _, selector := range s {
match, err := d.matchesSelector(selector)
if err != nil {
return false, err
}

result = result || match
}

return result, nil
}

func (d *GrafanaNotificationChannel) matchesSelector(s *metav1.LabelSelector) (bool, error) {
selector, err := metav1.LabelSelectorAsSelector(s)
if err != nil {
Expand Down
108 changes: 108 additions & 0 deletions api/integreatly/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 65 additions & 0 deletions bundle/manifests/integreatly.org_grafanafolders.yaml
@@ -0,0 +1,65 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.1
creationTimestamp: null
name: grafanafolders.integreatly.org
spec:
group: integreatly.org
names:
kind: GrafanaFolder
listKind: GrafanaFolderList
plural: grafanafolders
singular: grafanafolder
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: GrafanaFolder is the Schema for the grafana folders and folderpermissions
API
properties:
apiVersion:
description: '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'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
properties:
permissions:
items:
properties:
permissionLevel:
type: integer
permissionTarget:
type: string
permissionTargetType:
type: string
required:
- permissionLevel
- permissionTarget
- permissionTargetType
type: object
type: array
title:
type: string
required:
- title
type: object
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

0 comments on commit 3884b46

Please sign in to comment.