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

Folder permissions reconciler #821

Merged
merged 14 commits into from Sep 9, 2022
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: []