Skip to content

Commit

Permalink
Merge pull request #71 from vshn/uro3
Browse files Browse the repository at this point in the history
Uro3
zugao authored Mar 25, 2024

Verified

This commit was signed with the committer’s verified signature.
2 parents 10158da + 20725f0 commit ecf682b
Showing 34 changed files with 10,023 additions and 1,181 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -123,7 +123,7 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest

## Tool Versions
KUSTOMIZE_VERSION ?= v4.5.7
CONTROLLER_TOOLS_VERSION ?= v0.9.0
CONTROLLER_TOOLS_VERSION ?= v0.14.0

KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"
.PHONY: kustomize
2 changes: 2 additions & 0 deletions api/v1alpha1/stardoginstance_types.go
Original file line number Diff line number Diff line change
@@ -13,6 +13,8 @@ type StardogInstanceSpec struct {
// This is used by the Operator to make changes in the roles, permissions and users.
// +kubebuilder:validation:Required
AdminCredentials StardogUserCredentialsSpec `json:"adminCredentials,omitempty"`
// Disabled whether this instance is disabled or enabled for operator to recycle resources
Disabled bool `json:"disabled,omitempty"`
}

// StardogInstanceStatus defines the observed state of StardogInstance
1 change: 0 additions & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

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

16 changes: 11 additions & 5 deletions api/v1beta1/database_types.go
Original file line number Diff line number Diff line change
@@ -20,6 +20,11 @@ type DatabaseSpec struct {
// DatabaseName the database name that has to be created in the Stardog server
DatabaseName string `json:"databaseName,omitempty"`

//+kubebuilder:validation:optional
// AddUserForNonHiddenGraphs a dynamically managed user of this db with custom permissions
// Mainly used to not have access to hidden graphs
AddUserForNonHiddenGraphs string `json:"addUserForNonHiddenGraphs,omitempty"`

//+kubebuilder:validation:optional
// Options is the Stardog configuration options for this database. Only json input is valid.
Options string `json:"options,omitempty"`
@@ -35,11 +40,12 @@ type DatabaseSpec struct {

// DatabaseStatus defines the observed state of the Database
type DatabaseStatus struct {
DatabaseName string `json:"databaseName,omitempty"`
NamedGraphPrefix string `json:"namedGraphPrefix,omitempty"`
Options string `json:"options,omitempty"`
StardogInstanceRefs []StardogInstanceRef `json:"stardogInstanceRef,omitempty"`
Conditions []v1alpha1.StardogCondition `json:"conditions,omitempty"`
DatabaseName string `json:"databaseName,omitempty"`
AddUserForNonHiddenGraphs string `json:"addUserForNonHiddenGraphs,omitempty"`
NamedGraphPrefix string `json:"namedGraphPrefix,omitempty"`
Options string `json:"options,omitempty"`
StardogInstanceRefs []StardogInstanceRef `json:"stardogInstanceRef,omitempty"`
Conditions []v1alpha1.StardogCondition `json:"conditions,omitempty"`
}

//+kubebuilder:object:root=true
53 changes: 47 additions & 6 deletions api/v1beta1/organization_types.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,49 @@
package v1beta1

import (
"fmt"
"github.com/vshn/stardog-userrole-operator/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// OrganizationSpec defines the desired state of an Organization
type OrganizationSpec struct {
//+kubebuilder:validation:required
// +kubebuilder:validation:required
// Name is the short name of an organization
Name string `json:"name,omitempty"`

//+kubebuilder:validation:required
// +kubebuilder:validation:required
// DisplayName is the long name of an organization
DisplayName string `json:"displayName,omitempty"`

//+kubebuilder:validation:required
// +kubebuilder:validation:required
// DatabaseRef is the name of the Database this Organization is assigned to
DatabaseRef string `json:"databaseRef,omitempty"`

//+kubebuilder:validation:required
// +kubebuilder:validation:required
// NamedGraphs are the suffix graph names for this organization. The prefix can be found in the Database resource.
// The final graphs is defined as prefix + "/" + orgName + "/" suffix
NamedGraphs []string `json:"namedGraphs,omitempty"`
NamedGraphs []NamedGraph `json:"namedGraphs,omitempty"`
}

// NamedGraph defines the name and if necessary add another hidden named graph for this named graph
type NamedGraph struct {
// +kubebuilder:validation:required
// The name of the Named Graph
Name string `json:"name,omitempty"`

// +kubebuilder:validation:optional
// +kubebuilder:default=false
// AddHidden adds another graph with the same name but with a prefix "<named-graph-name/hidden>"
AddHidden bool `json:"addHidden"`
}

// OrganizationStatus defines the observed state of the Organization
type OrganizationStatus struct {
Name string `json:"name,omitempty"`
DisplayName string `json:"displayName,omitempty"`
DatabaseRef string `json:"databaseRef,omitempty"`
NamedGraphs []string `json:"namedGraphs,omitempty"`
NamedGraphs []NamedGraph `json:"namedGraphs,omitempty"`
StardogInstanceRefs []StardogInstanceRef `json:"stardogInstanceRefs,omitempty"`
Conditions []v1alpha1.StardogCondition `json:"conditions,omitempty"`
}
@@ -60,3 +73,31 @@ type OrganizationList struct {
func init() {
SchemeBuilder.Register(&Organization{}, &OrganizationList{})
}

func GetNamedGraphNames(namedGraphs []NamedGraph) []string {
names := make([]string, len(namedGraphs))
for _, graph := range namedGraphs {
names = append(names, graph.Name)
}
return names
}

func GetHiddenNamedGraphNames(namedGraphs []NamedGraph) []string {
names := make([]string, len(namedGraphs))
for _, graph := range namedGraphs {
if graph.AddHidden {
names = append(names, graph.Name)
}
}
return names
}

// FindNamedGraphByName finds the NamedGraph by name from a slice of NamedGraphs
func FindNamedGraphByName(graphs []NamedGraph, name string) (NamedGraph, error) {
for _, g := range graphs {
if g.Name == name {
return g, nil
}
}
return NamedGraph{}, fmt.Errorf("cannot find graph from name %s", name)
}
20 changes: 17 additions & 3 deletions api/v1beta1/zz_generated.deepcopy.go

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

109 changes: 0 additions & 109 deletions config/crd/bases/stardog.vshn.ch_databases.yaml

This file was deleted.

106 changes: 0 additions & 106 deletions config/crd/bases/stardog.vshn.ch_organizations.yaml

This file was deleted.

89 changes: 0 additions & 89 deletions config/crd/bases/stardog.vshn.ch_stardoginstances.yaml

This file was deleted.

121 changes: 0 additions & 121 deletions config/crd/bases/stardog.vshn.ch_stardogroles.yaml

This file was deleted.

94 changes: 0 additions & 94 deletions config/crd/bases/stardog.vshn.ch_stardogusers.yaml

This file was deleted.

1 change: 0 additions & 1 deletion config/rbac/role.yaml
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: manager-role
rules:
- apiGroups:
175 changes: 100 additions & 75 deletions controllers/database_controller.go
Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@ import (
"github.com/go-logr/logr"
"github.com/go-openapi/runtime"
"github.com/sethvargo/go-password/password"
stardogv1alpha1 "github.com/vshn/stardog-userrole-operator/api/v1alpha1"
stardogv1beta1 "github.com/vshn/stardog-userrole-operator/api/v1beta1"
stardog "github.com/vshn/stardog-userrole-operator/stardogrest/client"
"github.com/vshn/stardog-userrole-operator/stardogrest/client/db"
"github.com/vshn/stardog-userrole-operator/stardogrest/client/roles"
@@ -16,19 +18,15 @@ import (
"github.com/vshn/stardog-userrole-operator/stardogrest/models"
"k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
scheme "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"
"k8s.io/utils/strings/slices"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

stardogv1alpha1 "github.com/vshn/stardog-userrole-operator/api/v1alpha1"
stardogv1beta1 "github.com/vshn/stardog-userrole-operator/api/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"

"k8s.io/apimachinery/pkg/types"
)

const databaseFinalizer = "finalizer.stardog.databases"
@@ -127,6 +125,7 @@ func (r *DatabaseReconciler) reconcileDatabase(dr *DatabaseReconciliation) (ctrl
}

database.Status.StardogInstanceRefs = database.Spec.StardogInstanceRefs
database.Status.AddUserForNonHiddenGraphs = database.Spec.AddUserForNonHiddenGraphs
rc.SetStatusCondition(createStatusConditionReady(true, "Synchronized"))
return ctrl.Result{Requeue: true, RequeueAfter: ReconFreq}, r.updateStatus(dr)
}
@@ -191,9 +190,13 @@ func (r *DatabaseReconciler) deleteDatabase(dr *DatabaseReconciliation, instance
database := dr.resource

r.Log.V(1).Info("setup Stardog Client from ", "ref", instance)
auth, err := dr.reconciliationContext.initStardogClientFromRef(r.Client, instance)
auth, disabled, err := dr.reconciliationContext.initStardogClientFromRef(r.Client, instance)
if err != nil {
return err
return fmt.Errorf("cannot initialize stardog client: %v", err)
}
if disabled {
r.Log.Info("skipping resource from reconciliation", "instance", instance.Name, "resource", dr.resource.Name)
return nil
}

stardogClient := dr.reconciliationContext.stardogClient
@@ -213,22 +216,7 @@ func (r *DatabaseReconciler) deleteDatabase(dr *DatabaseReconciliation, instance
}

read, write := getUserRoleNames(database.Spec.DatabaseName)

// Remove permissions
permReadParam := roles_permissions.NewRemoveRolePermissionParams().WithRole(read)
for _, p := range getDBReadPermissions(database) {
_, err = stardogClient.RolesPermissions.RemoveRolePermission(permReadParam.WithPermission(&p), auth)
if err != nil && !NotFound(err) {
return fmt.Errorf("cannot remove permission %#v of role %s: %v", p, read, err)
}
}
permWriteParam := roles_permissions.NewRemoveRolePermissionParams().WithRole(write)
for _, p := range getDBWritePermissions(database) {
_, err = stardogClient.RolesPermissions.RemoveRolePermission(permWriteParam.WithPermission(&p), auth)
if err != nil && !NotFound(err) {
return fmt.Errorf("cannot remove permission %#v of role %s: %v", p, write, err)
}
}
customUser := database.Status.AddUserForNonHiddenGraphs

// Remove assigned roles to users
param := users_roles.NewRemoveRoleOfUserParams()
@@ -267,6 +255,11 @@ func (r *DatabaseReconciler) deleteDatabase(dr *DatabaseReconciliation, instance
return fmt.Errorf("cannot remove write user %s: %v", write, err)
}

// remove the custom user associated with this db
err = deleteCustomUser(stardogClient, customUser, auth)
if err != nil {
return fmt.Errorf("cannot delete customUser user %s: %v", customUser, err)
}
// Remove database
params := db.NewDropDatabaseParams().WithDb(database.Spec.DatabaseName)
_, err = stardogClient.Db.DropDatabase(params, auth)
@@ -295,6 +288,7 @@ func (r *DatabaseReconciler) validateSpecification(ctx context.Context, database
// If status is not set for database name then we treat it as a creation (first object reconciliation)
if status.DatabaseName == "" {
status.NamedGraphPrefix = spec.NamedGraphPrefix
status.AddUserForNonHiddenGraphs = spec.AddUserForNonHiddenGraphs
status.StardogInstanceRefs = spec.StardogInstanceRefs
status.DatabaseName = spec.DatabaseName
status.Options = spec.Options
@@ -340,11 +334,17 @@ func (r *DatabaseReconciler) sync(dr *DatabaseReconciliation, instance stardogv1
rc := dr.reconciliationContext
stardogClient := dr.reconciliationContext.stardogClient
database := dr.resource
customUser := database.Spec.AddUserForNonHiddenGraphs
customUserEnabled := customUser != ""

auth, err := rc.initStardogClientFromRef(r.Client, instance)
auth, disabled, err := rc.initStardogClientFromRef(r.Client, instance)
if err != nil {
return fmt.Errorf("cannot initialize stardog client: %v", err)
}
if disabled {
r.Log.Info("skipping resource from reconciliation", "instance", instance.Name, "resource", dr.resource.Name)
return nil
}

// Generate and save credentials in k8s
secretName := getUsersCredentialSecret(database.Spec.DatabaseName, instance.Name)
@@ -378,93 +378,118 @@ func (r *DatabaseReconciler) sync(dr *DatabaseReconciliation, instance stardogv1
{Password: []string{readPwd}, Username: &readName},
{Password: []string{writePwd}, Username: &writeName},
}
usrs, err = createDefaultUsersForDB(stardogClient, auth, usrs)
rolenames := []models.Rolename{
{Rolename: &readName},
{Rolename: &writeName},
}
if customUserEnabled {
customUsrPwd, err := generatePassword()
if err != nil {
return err
}
usrs = append(usrs, models.User{Password: []string{customUsrPwd}, Username: &customUser})
rolenames = append(rolenames, models.Rolename{Rolename: &customUser})
}

createdUsrs, err := createDefaultUsersForDB(stardogClient, auth, usrs)
if err != nil {
r.Log.Error(err, "error creating users", "users", usrs)
return err
}
// don't create any credential secret if no users have been created in Stardog
if len(usrs) != 0 {
err = r.createCredentials(dr, secretName, usrs)
if len(createdUsrs) != 0 {
err = r.createCredentials(dr, secretName, createdUsrs)
if err != nil {
r.Log.Error(err, "error creating secret credentials", "users", usrs)
r.Log.Error(err, "error creating secret credentials", "users", createdUsrs)
return err
}
}

// create default read and write roles
rolenames := []models.Rolename{
{Rolename: &readName},
{Rolename: &writeName},
}
err = createDefaultRolesForDB(stardogClient, auth, rolenames)
if err != nil {
r.Log.Error(err, "error creating roles", "roles", rolenames)
return err
}

//create read and write permissions for user roles
readPerms := getDBReadPermissions(database)
readPerms := getDBReadPermissions(database.Spec.DatabaseName)
writePerms := getDBWritePermissions(database.Spec.DatabaseName)

err = createDefaultPermissions(stardogClient, auth, readName, readPerms)
if err != nil {
r.Log.Error(err, "adding permission to role failed", "role", readName, "permission", readPerms)
return err
}

writePerms := getDBWritePermissions(database)
err = createDefaultPermissions(stardogClient, auth, writeName, writePerms)
err = createDefaultPermissions(stardogClient, auth, writeName, append(writePerms, readPerms...))
if err != nil {
r.Log.Error(err, "adding permission to role failed", "role", writeName, "permission", writePerms)
return err
}

if customUserEnabled {
perms := readPerms
err = createDefaultPermissions(stardogClient, auth, customUser, perms)
if err != nil {
r.Log.Error(err, "adding permission to role failed", "role", writeName, "permission", writePerms)
return err
}
}

// assign roles to users
err = assignDefaultRoles(stardogClient, auth, readName, writeName)
err = assignDefaultRoles(stardogClient, auth, usrs)
if err != nil {
r.Log.Error(err, "error assigning roles to users")
return err
}

// delete custom user in case it has been removed or changed from the resource
statusCustomUser := database.Status.AddUserForNonHiddenGraphs
if (statusCustomUser != "" && customUser == "") || (statusCustomUser != "" && statusCustomUser != customUser) {
err = deleteCustomUser(stardogClient, statusCustomUser, auth)
if err != nil {
return fmt.Errorf("cannot delete custom user %s: %v", customUser, err)
}
}

return nil
}

func assignDefaultRoles(stardogClient *stardog.Stardog, auth runtime.ClientAuthInfoWriter, readName, writeName string) error {
readUserRolesResp, err := stardogClient.UsersRoles.ListUserRoles(users_roles.NewListUserRolesParams().WithUser(readName), auth)
if err != nil || !readUserRolesResp.IsSuccess() {
return fmt.Errorf("error getting user roles for user %s: %v", readName, err)
func deleteCustomUser(stardogClient *stardog.Stardog, name string, auth runtime.ClientAuthInfoWriter) error {
_, err := stardogClient.UsersRoles.RemoveRoleOfUser(users_roles.NewRemoveRoleOfUserParams().WithUser(name).WithRole(name), auth)
if err != nil && !NotFound(err) {
return fmt.Errorf("cannot remove assigned role %s from user %s: %v", name, name, err)
}

if !slices.Contains(readUserRolesResp.Payload.Roles, readName) {
params := users_roles.NewAddRoleParams().
WithUser(readName).
WithRole(&models.Rolename{Rolename: &readName})
roleResp, err := stardogClient.UsersRoles.AddRole(params, auth)
if err != nil || !roleResp.IsSuccess() {
return fmt.Errorf("error assigning role %s to user %s: %v", readName, readName, err)
}
_, err = stardogClient.Roles.RemoveRole(roles.NewRemoveRoleParams().WithRole(name), auth)
if err != nil && !NotFound(err) {
return fmt.Errorf("cannot remove customUser role %s: %v", name, err)
}

writeUserRolesResp, err := stardogClient.UsersRoles.ListUserRoles(users_roles.NewListUserRolesParams().WithUser(writeName), auth)
if err != nil || !writeUserRolesResp.IsSuccess() {
return fmt.Errorf("error getting user roles for user %s: %v", writeName, err)
_, err = stardogClient.Users.RemoveUser(users.NewRemoveUserParams().WithUser(name), auth)
if err != nil && !NotFound(err) {
return fmt.Errorf("cannot remove customUser user %s: %v", name, err)
}
return nil
}

if !slices.Contains(writeUserRolesResp.Payload.Roles, readName) {
params := users_roles.NewAddRoleParams().
WithUser(writeName).
WithRole(&models.Rolename{Rolename: &readName})
roleResp, err := stardogClient.UsersRoles.AddRole(params, auth)
if err != nil || !roleResp.IsSuccess() {
return fmt.Errorf("error assigning role %s to user %s: %v", readName, writeName, err)
func assignDefaultRoles(stardogClient *stardog.Stardog, auth runtime.ClientAuthInfoWriter, usrs []models.User) error {
for _, usr := range usrs {
username := *usr.Username
//role name is the same as username
rolename := *usr.Username
resp, err := stardogClient.UsersRoles.ListUserRoles(users_roles.NewListUserRolesParams().WithUser(username), auth)
if err != nil || !resp.IsSuccess() {
return fmt.Errorf("error getting user roles for user %s: %v", username, err)
}
}

if !slices.Contains(writeUserRolesResp.Payload.Roles, writeName) {
params := users_roles.NewAddRoleParams().
WithUser(writeName).
WithRole(&models.Rolename{Rolename: &writeName})
roleResp, err := stardogClient.UsersRoles.AddRole(params, auth)
if err != nil || !roleResp.IsSuccess() {
return fmt.Errorf("error assigning role %s to user %s: %v", writeName, writeName, err)
if !slices.Contains(resp.Payload.Roles, username) {
params := users_roles.NewAddRoleParams().
WithUser(username).
WithRole(&models.Rolename{Rolename: &rolename})
roleResp, err := stardogClient.UsersRoles.AddRole(params, auth)
if err != nil || !roleResp.IsSuccess() {
return fmt.Errorf("error assigning role %s to user %s: %v", username, rolename, err)
}
}
}
return nil
@@ -626,31 +651,31 @@ func createDefaultRolesForDB(stardogClient *stardog.Stardog, auth runtime.Client
return nil
}

func getDBWritePermissions(database *stardogv1beta1.Database) []models.Permission {
func getDBWritePermissions(database string) []models.Permission {
return []models.Permission{
{
Action: pointer.String("WRITE"),
Resource: []string{database.Spec.DatabaseName},
Resource: []string{database},
ResourceType: pointer.String("db"),
},
{
Action: pointer.String("WRITE"),
Resource: []string{database.Spec.DatabaseName},
Resource: []string{database},
ResourceType: pointer.String("metadata"),
},
}
}

func getDBReadPermissions(database *stardogv1beta1.Database) []models.Permission {
func getDBReadPermissions(database string) []models.Permission {
return []models.Permission{
{
Action: pointer.String("READ"),
Resource: []string{database.Spec.DatabaseName},
Resource: []string{database},
ResourceType: pointer.String("db"),
},
{
Action: pointer.String("READ"),
Resource: []string{database.Spec.DatabaseName},
Resource: []string{database},
ResourceType: pointer.String("metadata"),
},
}
165 changes: 165 additions & 0 deletions controllers/database_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package controllers

import (
"context"
"github.com/go-logr/logr/testr"
"github.com/go-openapi/runtime"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/vshn/stardog-userrole-operator/api/v1alpha1"
"github.com/vshn/stardog-userrole-operator/api/v1beta1"
db2 "github.com/vshn/stardog-userrole-operator/stardogrest/client/db"
"github.com/vshn/stardog-userrole-operator/stardogrest/client/roles"
"github.com/vshn/stardog-userrole-operator/stardogrest/client/roles_permissions"
"github.com/vshn/stardog-userrole-operator/stardogrest/client/users"
"github.com/vshn/stardog-userrole-operator/stardogrest/client/users_roles"
stardogmock "github.com/vshn/stardog-userrole-operator/stardogrest/mocks"
"github.com/vshn/stardog-userrole-operator/stardogrest/models"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"testing"
)

func Test_createCustomUser(t *testing.T) {

namespace := "namespace-test"
stardogInstanceName := "instance-test"
secretName := "secret-test"
serverURL := "http://url-test.ch"
username := "admin"
password := "1234"
err := v1alpha1.AddToScheme(scheme.Scheme)
assert.NoError(t, err)
err = v1beta1.AddToScheme(scheme.Scheme)
assert.NoError(t, err)

mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
stardogMocked := stardogmock.NewMockStardogTestClient(mockCtrl)
stardogClient := createStardogClientFromMock(stardogMocked)
db := createStardogDB("test-db", "hidden-user", v1beta1.StardogInstanceRef{
Name: stardogInstanceName,
Namespace: namespace,
})
org := createOrg("org-test", db.Name, []v1beta1.NamedGraph{{
Name: "graph1",
AddHidden: true,
}})

tests := []struct {
name string
stardogInstance v1alpha1.StardogInstance
secret v1.Secret
stardogDB *v1beta1.Database
stardogOrg *v1beta1.Organization
dr DatabaseReconciliation
expectedCustomUser bool
expectedCustomRole bool
expectedWritePermissions bool
}{
{
name: "GivenReconciliationContext_WhenCreateDatabaseWithCustomUser_ThenCreateCustomUser",
stardogInstance: *createStardogInstanceWithFinalizers(namespace, stardogInstanceName, secretName, serverURL),
secret: *createFullSecret(namespace, secretName, username, password),
stardogDB: db,
stardogOrg: org,
dr: DatabaseReconciliation{
resource: db,
reconciliationContext: &ReconciliationContext{
context: context.Background(),
conditions: make(map[v1alpha1.StardogConditionType]v1alpha1.StardogCondition),
stardogClient: stardogClient,
},
},
expectedCustomUser: true,
expectedCustomRole: true,
expectedWritePermissions: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeKubeClient, err := createKubeFakeClientWithSub(tt.stardogDB, &tt.stardogInstance, &tt.secret)
assert.NoError(t, err)
r := DatabaseReconciler{
Log: testr.New(t),
Scheme: scheme.Scheme,
Client: fakeKubeClient,
}

customUserExists := false
writePermissions := false
stardogMocked.EXPECT().
SetTransport(gomock.Any()).
AnyTimes()
stardogMocked.EXPECT().
ListDatabases(gomock.Any(), gomock.Any(), gomock.Any()).
Return(&db2.ListDatabasesOK{Payload: &models.Databases{}}, nil).
Times(1)
stardogMocked.EXPECT().
CreateNewDatabase(gomock.Any(), gomock.Any(), gomock.Any()).
Return(db2.NewCreateNewDatabaseCreated(), nil).
Times(1)
stardogMocked.EXPECT().
ListUsers(gomock.Any(), gomock.Any()).
Return(&users.ListUsersOK{Payload: &models.Users{}}, nil).
Times(1)
stardogMocked.EXPECT().
CreateUser(gomock.Any(), gomock.Any(), gomock.Any()).
DoAndReturn(func(params *users.CreateUserParams, authInfo runtime.ClientAuthInfoWriter, opts ...users.ClientOption) (*users.CreateUserCreated, error) {
if *params.User.Username == "hidden-user" {
customUserExists = true
}
return users.NewCreateUserCreated(), nil
}).
Times(3)
stardogMocked.EXPECT().
ListRoles(gomock.Any(), gomock.Any()).
Return(&roles.ListRolesOK{Payload: &models.Roles{}}, nil).
Times(1)
stardogMocked.EXPECT().
CreateRole(gomock.Any(), gomock.Any(), gomock.Any()).
Return(roles.NewCreateRoleCreated(), nil).
Times(3)
stardogMocked.EXPECT().
ListRolePermissions(gomock.Any(), gomock.Any()).
Return(&roles_permissions.ListRolePermissionsOK{Payload: &models.Permissions{}}, nil).
Times(3)
stardogMocked.EXPECT().
ListUserRoles(gomock.Any(), gomock.Any()).
Return(&users_roles.ListUserRolesOK{Payload: &models.Roles{}}, nil).
Times(3)
stardogMocked.EXPECT().
AddRole(gomock.Any(), gomock.Any()).
Return(users_roles.NewAddRoleNoContent(), nil).
Times(3)
stardogMocked.EXPECT().
AddRolePermission(gomock.Any(), gomock.Any(), gomock.Any()).
DoAndReturn(func(params *roles_permissions.AddRolePermissionParams, authInfo runtime.ClientAuthInfoWriter, opts ...users_roles.ClientOption) (*roles_permissions.AddRolePermissionCreated, error) {
if *params.Permission.Action == "WRITE" && params.Role == "hidden-user" {
writePermissions = true
}
return roles_permissions.NewAddRolePermissionCreated(), nil
}).AnyTimes()

_, err = r.reconcileDatabase(&tt.dr)
assert.Equal(t, tt.expectedCustomUser, customUserExists)
assert.Equal(t, tt.expectedWritePermissions, writePermissions)
})
}
}

func createStardogDB(name, hiddenUser string, instanceRef v1beta1.StardogInstanceRef) *v1beta1.Database {
return &v1beta1.Database{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1beta1.DatabaseSpec{
DatabaseName: name,
AddUserForNonHiddenGraphs: hiddenUser,
StardogInstanceRefs: []v1beta1.StardogInstanceRef{instanceRef},
NamedGraphPrefix: "https://graph.ch",
},
}
}
195 changes: 173 additions & 22 deletions controllers/organization_controller.go
Original file line number Diff line number Diff line change
@@ -23,7 +23,12 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sort"
"strings"
"time"
)

const orgFinalizer = "finalizer.stardog.organizations"
@@ -174,13 +179,13 @@ func (r *OrganizationReconciler) validateSpecification(ctx context.Context, orga
}

func (r *OrganizationReconciler) syncOrganization(or *OrganizationReconciliation) error {
dbInstances := or.database.Status.StardogInstanceRefs
dbInstances := or.database.Spec.StardogInstanceRefs
orgInstances := or.resource.Status.StardogInstanceRefs

// Create an organization for each instance in spec.StardogInstanceRefs
for _, instance := range dbInstances {
if err := r.sync(or, instance); err != nil {
return fmt.Errorf("unable to sync instance %s for organization %s", instance.Name, or.resource.Name)
return fmt.Errorf("unable to sync instance %s for organization %s: %v", instance.Name, or.resource.Name, err)
}
}

@@ -202,10 +207,14 @@ func (r *OrganizationReconciler) sync(or *OrganizationReconciliation, instance s
orgName := org.Spec.Name
stardogClient := or.reconciliationContext.stardogClient

auth, err := rc.initStardogClientFromRef(r.Client, instance)
auth, disabled, err := rc.initStardogClientFromRef(r.Client, instance)
if err != nil {
return fmt.Errorf("cannot initialize stardog client: %v", err)
}
if disabled {
r.Log.Info("skipping resource from reconciliation", "instance", instance.Name, "resource", or.resource.Name)
return nil
}

// Generate and save credentials in k8s
secretName := getUsersCredentialSecret(dbName, orgName)
@@ -246,7 +255,7 @@ func (r *OrganizationReconciler) sync(or *OrganizationReconciliation, instance s
}

//create read and write permissions for user roles
perms := getOrganizationPerms(database, org, dbName)
perms := getOrganizationPerms(database, org, true, false)
err = createDefaultPermissions(stardogClient, auth, userRoleName, perms)
if err != nil {
r.Log.Error(err, "Adding permission to role failed", "role", userRoleName, "permission", perms)
@@ -266,6 +275,22 @@ func (r *OrganizationReconciler) sync(or *OrganizationReconciliation, instance s
r.Log.Error(err, "Cannot assign defaultRoles", "user", userRoleName)
return err
}

//create read permission for public user for this organisation
roleNameCustomUser := database.Spec.AddUserForNonHiddenGraphs
permsCustomUser := getOrganizationPerms(database, org, false, true)
err = createDefaultPermissions(stardogClient, auth, roleNameCustomUser, permsCustomUser)
if err != nil {
r.Log.Error(err, "Adding permission to role failed", "role", roleNameCustomUser, "permission", permsCustomUser)
return err
}

err = adjustPermissionsForCustomUser(org, database, stardogClient, auth, roleNameCustomUser)
if err != nil {
r.Log.Error(err, "Cannot remove permissions")
return err
}

return nil
}

@@ -287,18 +312,72 @@ func assignDefaultRole(stardogClient *stardog.Stardog, auth runtime.ClientAuthIn
return nil
}

func adjustPermissionsForCustomUser(org *stardogv1beta1.Organization, database *stardogv1beta1.Database, stardogClient *stardog.Stardog, auth runtime.ClientAuthInfoWriter, userRoleName string) error {
for _, statusGraph := range org.Status.NamedGraphs {
statusGraphName := statusGraph.Name
perm := roles_permissions.NewRemoveRolePermissionParams().WithRole(userRoleName)
if !contains(stardogv1beta1.GetNamedGraphNames(org.Spec.NamedGraphs), statusGraph.Name) {
return removePermissionForCustomUser(org, database, stardogClient, auth, perm, statusGraphName)
}
}
return nil
}

func removePermissionForCustomUser(org *stardogv1beta1.Organization, database *stardogv1beta1.Database, stardogClient *stardog.Stardog, auth runtime.ClientAuthInfoWriter, perm *roles_permissions.RemoveRolePermissionParams, statusGraphName string) error {
ng := getFullNamedGraph(org.Spec.Name, database.Spec.NamedGraphPrefix, statusGraphName, false)
resources := []string{ng, database.Spec.DatabaseName}
sort.Strings(resources)
p := models.Permission{
Action: pointer.String("READ"),
Resource: resources,
ResourceType: pointer.String("named-graph"),
}
pResp, err := stardogClient.RolesPermissions.RemoveRolePermission(perm.WithPermission(&p), auth)
if err != nil || !pResp.IsSuccess() {
return fmt.Errorf("cannot remove permission %+v for graph %s: %v", p, ng, err)
}
return nil
}

func removePermissions(org *stardogv1beta1.Organization, database *stardogv1beta1.Database, stardogClient *stardog.Stardog, auth runtime.ClientAuthInfoWriter, userRoleName string) error {
for _, graph := range org.Status.NamedGraphs {
if !contains(org.Spec.NamedGraphs, graph) {
perm := roles_permissions.NewRemoveRolePermissionParams().WithRole(userRoleName)
ng := getFullNamedGraph(org.Spec.Name, database.Spec.NamedGraphPrefix, graph)
for _, statusGraph := range org.Status.NamedGraphs {
perm := roles_permissions.NewRemoveRolePermissionParams().WithRole(userRoleName)
if !contains(stardogv1beta1.GetNamedGraphNames(org.Spec.NamedGraphs), statusGraph.Name) {
ng := getFullNamedGraph(org.Spec.Name, database.Spec.NamedGraphPrefix, statusGraph.Name, false)
for _, p := range getGraphPermissionForNameGraphs(ng, database.Spec.DatabaseName) {
pResp, err := stardogClient.RolesPermissions.RemoveRolePermission(perm.WithPermission(&p), auth)
if err != nil || !pResp.IsSuccess() {
return fmt.Errorf("cannot remove permission %+v for graph %s: %v", p, ng, err)
return fmt.Errorf("cannot remove permission %+v for graph %s of role %s: %v", p, ng, userRoleName, err)
}
}

// If a NamedGraph has AddHidden then remove the permission from the hidden graph as well
if statusGraph.AddHidden {
ngh := getFullNamedGraph(org.Spec.Name, database.Spec.NamedGraphPrefix, statusGraph.Name, true)
for _, p := range getGraphPermissionForNameGraphs(ngh, database.Spec.DatabaseName) {
pResp, err := stardogClient.RolesPermissions.RemoveRolePermission(perm.WithPermission(&p), auth)
if err != nil || !pResp.IsSuccess() {
return fmt.Errorf("cannot remove permission %+v for graph %s of user %s: %v", p, ngh, userRoleName, err)
}
}
}
} else {
// If the named graph still exists but has AddHidden true then delete the hidden graph
specGraph, err := stardogv1beta1.FindNamedGraphByName(org.Spec.NamedGraphs, statusGraph.Name)
if err != nil {
return fmt.Errorf("cannot find spec graph by name %s: %v", statusGraph.Name, err)
}
if specGraph.AddHidden == false && statusGraph.AddHidden == true {
ngh := getFullNamedGraph(org.Spec.Name, database.Spec.NamedGraphPrefix, statusGraph.Name, true)
for _, p := range getGraphPermissionForNameGraphs(ngh, database.Spec.DatabaseName) {
pResp, err := stardogClient.RolesPermissions.RemoveRolePermission(perm.WithPermission(&p), auth)
if err != nil || !pResp.IsSuccess() {
return fmt.Errorf("cannot remove permission %+v for graph %s of role %s: %v", p, ngh, userRoleName, err)
}
}
}
}

}
return nil
}
@@ -343,9 +422,14 @@ func (r *OrganizationReconciler) deleteOrganization(or *OrganizationReconciliati
orgName := org.Spec.Name

r.Log.V(1).Info("setup Stardog Client from ", "ref", instance)
auth, err := or.reconciliationContext.initStardogClientFromRef(r.Client, instance)
auth, disabled, err := or.reconciliationContext.initStardogClientFromRef(r.Client, instance)
if err != nil {
return err
return fmt.Errorf("cannot initialize stardog client: %v", err)

}
if disabled {
r.Log.Info("skipping resource from reconciliation", "instance", instance.Name, "resource", or.resource.Name)
return nil
}

stardogClient := or.reconciliationContext.stardogClient
@@ -355,13 +439,23 @@ func (r *OrganizationReconciler) deleteOrganization(or *OrganizationReconciliati

// Remove all permissions
permParam := roles_permissions.NewRemoveRolePermissionParams().WithRole(userRoleName)
for _, p := range getOrganizationPerms(database, org, dbName) {
for _, p := range getOrganizationPerms(database, org, true, false) {
_, err = stardogClient.RolesPermissions.RemoveRolePermission(permParam.WithPermission(&p), auth)
if err != nil && !NotFound(err) {
return fmt.Errorf("cannot remove permission %#v of role %s: %v", p, userRoleName, err)
}
}

// Adjust permissions for custom user from the database if exists
customUser := database.Spec.AddUserForNonHiddenGraphs
if customUser != "" {
for _, statusGraph := range org.Status.NamedGraphs {
statusGraphName := statusGraph.Name
perm := roles_permissions.NewRemoveRolePermissionParams().WithRole(customUser)
return removePermissionForCustomUser(org, database, stardogClient, auth, perm, statusGraphName)
}
}

// Remove assigned role to user
param := users_roles.NewRemoveRoleOfUserParams()
_, err = stardogClient.UsersRoles.RemoveRoleOfUser(param.WithUser(userRoleName).WithRole(userRoleName), auth)
@@ -438,11 +532,36 @@ func (r *OrganizationReconciler) createCredentials(or *OrganizationReconciliatio

// SetupWithManager sets up the controller with the Manager.
func (r *OrganizationReconciler) SetupWithManager(mgr ctrl.Manager) error {
h := handler.EnqueueRequestsFromMapFunc(triggerOrgReconciliationFromDB(mgr.GetClient()))
return ctrl.NewControllerManagedBy(mgr).
For(&stardogv1beta1.Organization{}).
Watches(&stardogv1beta1.Database{}, h).
Complete(r)
}

// triggerOrgReconciliation Trigger a reconciliation of all organizations of this database
// This is required to update the custom user permissions from the organizations linked to this db
func triggerOrgReconciliationFromDB(c client.Client) handler.MapFunc {
return func(ctx context.Context, db client.Object) []reconcile.Request {
l := log.FromContext(ctx).WithName("triggerOrgReconciliation")
var orgList stardogv1beta1.OrganizationList
if err := c.List(ctx, &orgList); err != nil {
l.Error(err, "failed to get organization list")
return nil
}

reqs := make([]reconcile.Request, 0)
for _, o := range orgList.Items {
if db.GetName() == o.Spec.DatabaseRef {
reqs = append(reqs, reconcile.Request{NamespacedName: types.NamespacedName{Name: o.GetName()}})
}
}
// Wait until database reconciled
time.Sleep(time.Second * 3)
return reqs
}
}

func getLoggingKeysAndValuesForOrganization(organization *stardogv1beta1.Organization) []interface{} {
return []interface{}{
"StardogOrganization", organization.Namespace + "/" + organization.Name,
@@ -453,47 +572,79 @@ func getUserAndRoleName(dbName, orgName string) string {
return fmt.Sprintf("%s-%s", dbName, orgName)
}

func getGraphPermissions(org *stardogv1beta1.Organization, namedGraphPrefix, dbName string) []models.Permission {
func getGraphPermissions(org *stardogv1beta1.Organization, namedGraphPrefix, dbName string, withHidden, readOnly bool) []models.Permission {
perms := make([]models.Permission, 0)
orgName := org.Spec.Name
for _, ng := range org.Spec.NamedGraphs {
fullNameNG := getFullNamedGraph(org.Spec.Name, namedGraphPrefix, ng)
fullNameNG := getFullNamedGraph(orgName, namedGraphPrefix, ng.Name, false)
ngPerm := []models.Permission{
{
Action: pointer.String("READ"),
Resource: []string{dbName, fullNameNG},
ResourceType: pointer.String("named-graph"),
},
{
}
if !readOnly {
ngPerm = append(ngPerm, models.Permission{
Action: pointer.String("WRITE"),
Resource: []string{dbName, fullNameNG},
ResourceType: pointer.String("named-graph"),
},
})
}
if withHidden && ng.AddHidden {
fullNameNGH := getFullNamedGraph(orgName, namedGraphPrefix, ng.Name, true)
ngPermHidden := []models.Permission{
{
Action: pointer.String("READ"),
Resource: []string{dbName, fullNameNGH},
ResourceType: pointer.String("named-graph"),
},
}
if !readOnly {
ngPermHidden = append(ngPermHidden, models.Permission{
Action: pointer.String("WRITE"),
Resource: []string{dbName, fullNameNGH},
ResourceType: pointer.String("named-graph"),
})
}
perms = append(perms, ngPermHidden...)
}
perms = append(perms, ngPerm...)
}
return perms
}

func getFullNamedGraph(orgName, namedGraphPrefix string, ng string) string {
func getFullNamedGraph(orgName, namedGraphPrefix, ng string, hidden bool) string {
if hidden {
return strings.TrimSuffix(namedGraphPrefix, "/") + "/" + orgName + "/" + ng + "/hidden"
}
return strings.TrimSuffix(namedGraphPrefix, "/") + "/" + orgName + "/" + ng
}

func getGraphPermissionForNameGraphs(namedGraph, dbName string) []models.Permission {
resources := []string{namedGraph, dbName}
sort.Strings(resources)
return []models.Permission{
{
Action: pointer.String("READ"),
Resource: []string{namedGraph, dbName},
Resource: resources,
ResourceType: pointer.String("named-graph"),
},
{
Action: pointer.String("WRITE"),
Resource: []string{namedGraph, dbName},
Resource: resources,
ResourceType: pointer.String("named-graph"),
},
}
}

func getOrganizationPerms(database *stardogv1beta1.Database, org *stardogv1beta1.Organization, dbName string) []models.Permission {
dbPerms := append(getDBReadPermissions(database), getDBWritePermissions(database)...)
return append(dbPerms, getGraphPermissions(org, database.Spec.NamedGraphPrefix, dbName)...)
func getOrganizationPerms(database *stardogv1beta1.Database, org *stardogv1beta1.Organization, withHidden, readOnly bool) []models.Permission {
db := database.Spec.DatabaseName
graphPerm := getGraphPermissions(org, database.Spec.NamedGraphPrefix, db, withHidden, readOnly)
dbReadPerm := getDBReadPermissions(db)
perm := append(dbReadPerm, graphPerm...)
if readOnly {
return perm
}
return append(perm, getDBWritePermissions(db)...)
}
164 changes: 164 additions & 0 deletions controllers/organization_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package controllers

import (
"context"
"github.com/go-logr/logr/testr"
"github.com/go-openapi/runtime"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/vshn/stardog-userrole-operator/api/v1alpha1"
"github.com/vshn/stardog-userrole-operator/api/v1beta1"
"github.com/vshn/stardog-userrole-operator/stardogrest/client/roles"
"github.com/vshn/stardog-userrole-operator/stardogrest/client/roles_permissions"
"github.com/vshn/stardog-userrole-operator/stardogrest/client/users"
"github.com/vshn/stardog-userrole-operator/stardogrest/client/users_roles"
stardogmock "github.com/vshn/stardog-userrole-operator/stardogrest/mocks"
"github.com/vshn/stardog-userrole-operator/stardogrest/models"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"testing"
)

func Test_createHiddenGraph(t *testing.T) {

namespace := "namespace-test"
stardogInstanceName := "instance-test"
secretName := "secret-test"
serverURL := "http://url-test.ch"
username := "admin"
password := "1234"
action := "READ"
resourceTypeNG := "named-graph"
resourceTypeMeta := "metadata"
resourceTypeDB := "db"
err := v1alpha1.AddToScheme(scheme.Scheme)
assert.NoError(t, err)
err = v1beta1.AddToScheme(scheme.Scheme)
assert.NoError(t, err)

mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
stardogMocked := stardogmock.NewMockStardogTestClient(mockCtrl)
stardogClient := createStardogClientFromMock(stardogMocked)
db := createStardogDB("test-db", "hidden-user", v1beta1.StardogInstanceRef{
Name: stardogInstanceName,
Namespace: namespace,
})
org := createOrg("org-test", db.Name, []v1beta1.NamedGraph{{
Name: "graph1",
AddHidden: true,
}})

tests := []struct {
name string
stardogInstance v1alpha1.StardogInstance
secret v1.Secret
stardogDB *v1beta1.Database
stardogOrg *v1beta1.Organization
or OrganizationReconciliation
expectedPermissions []models.Permission
}{
{
name: "GivenReconciliationContext_WhenCreateOrgWithHiddenGraphs_ThenCreateHiddenGraphs",
stardogInstance: *createStardogInstanceWithFinalizers(namespace, stardogInstanceName, secretName, serverURL),
secret: *createFullSecret(namespace, secretName, username, password),
stardogDB: db,
stardogOrg: org,
or: OrganizationReconciliation{
database: db,
resource: org,
reconciliationContext: &ReconciliationContext{
context: context.Background(),
conditions: make(map[v1alpha1.StardogConditionType]v1alpha1.StardogCondition),
stardogClient: stardogClient,
},
},
expectedPermissions: []models.Permission{
{
Action: &action,
Resource: []string{"test-db"},
ResourceType: &resourceTypeDB,
},
{
Action: &action,
Resource: []string{"test-db"},
ResourceType: &resourceTypeMeta,
},
{
Action: &action,
Resource: []string{"test-db", "https://graph.ch/org-test/graph1"},
ResourceType: &resourceTypeNG,
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeKubeClient, err := createKubeFakeClientWithSub(tt.stardogOrg, tt.stardogDB, &tt.stardogInstance, &tt.secret)
assert.NoError(t, err)
r := OrganizationReconciler{
Log: testr.New(t),
Scheme: scheme.Scheme,
Client: fakeKubeClient,
}

stardogMocked.EXPECT().
SetTransport(gomock.Any()).
AnyTimes()
stardogMocked.EXPECT().
ListUsers(gomock.Any(), gomock.Any()).
Return(&users.ListUsersOK{Payload: &models.Users{}}, nil).
Times(1)
stardogMocked.EXPECT().
CreateUser(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).
Return(users.NewCreateUserCreated(), nil)
stardogMocked.EXPECT().
ListRoles(gomock.Any(), gomock.Any()).Times(1).
Return(&roles.ListRolesOK{Payload: &models.Roles{}}, nil)
stardogMocked.EXPECT().
CreateRole(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).
Return(roles.NewCreateRoleCreated(), nil)
stardogMocked.EXPECT().
ListRolePermissions(gomock.Any(), gomock.Any()).AnyTimes().
Return(&roles_permissions.ListRolePermissionsOK{Payload: &models.Permissions{}}, nil)
stardogMocked.EXPECT().
ListUserRoles(gomock.Any(), gomock.Any()).AnyTimes().
Return(&users_roles.ListUserRolesOK{Payload: &models.Roles{}}, nil)
stardogMocked.EXPECT().
AddRole(gomock.Any(), gomock.Any()).AnyTimes().
Return(users_roles.NewAddRoleNoContent(), nil)

hiddenRoleExists := false
var permHiddenRole []models.Permission
stardogMocked.EXPECT().
AddRolePermission(gomock.Any(), gomock.Any(), gomock.Any()).
DoAndReturn(func(params *roles_permissions.AddRolePermissionParams, authInfo runtime.ClientAuthInfoWriter, opts ...users_roles.ClientOption) (*roles_permissions.AddRolePermissionCreated, error) {
if params.Role == "hidden-user" {
hiddenRoleExists = true
permHiddenRole = append(permHiddenRole, *params.Permission)
}
return roles_permissions.NewAddRolePermissionCreated(), nil
}).AnyTimes()

_, err = r.reconcileOrganization(&tt.or)
assert.True(t, hiddenRoleExists, "hidden graph exists")
assert.Equal(t, tt.expectedPermissions, permHiddenRole)
})
}
}

func createOrg(name, dbRef string, ngs []v1beta1.NamedGraph) *v1beta1.Organization {
return &v1beta1.Organization{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1beta1.OrganizationSpec{
Name: name,
DisplayName: name,
DatabaseRef: dbRef,
NamedGraphs: ngs,
},
}
}
14 changes: 11 additions & 3 deletions controllers/reconciliation_types.go
Original file line number Diff line number Diff line change
@@ -80,14 +80,22 @@ func (rc *ReconciliationContext) initStardogClient(kubeClient client.Client, sta
return auth.BasicAuth(adminUsername, adminPassword), nil
}

func (rc *ReconciliationContext) initStardogClientFromRef(kubeClient client.Client, instance v1beta1.StardogInstanceRef) (runtime.ClientAuthInfoWriter, error) {
func (rc *ReconciliationContext) initStardogClientFromRef(kubeClient client.Client, instance v1beta1.StardogInstanceRef) (runtime.ClientAuthInfoWriter, bool, error) {
stardogInstance := &StardogInstance{}
err := kubeClient.Get(rc.context, types.NamespacedName{Namespace: instance.Namespace, Name: instance.Name}, stardogInstance)
if err != nil {
return nil, fmt.Errorf("cannot retrieve stardogInstanceRef %s/%s: %v", instance.Namespace, instance.Name, err)
return nil, true, fmt.Errorf("cannot retrieve stardogInstanceRef %s/%s: %v", instance.Namespace, instance.Name, err)
}
if stardogInstance.Spec.Disabled {
return nil, true, nil
}
rc.namespace = stardogInstance.Namespace
return rc.initStardogClient(kubeClient, *stardogInstance)
stardogClient, err := rc.initStardogClient(kubeClient, *stardogInstance)
if err != nil {
return nil, true, err
}

return stardogClient, false, nil
}

func (rc *ReconciliationContext) getCredentials(kubeClient client.Client, credentials StardogUserCredentialsSpec, alternativeNamespace string) (username, password string, err error) {
20 changes: 18 additions & 2 deletions controllers/reconciliation_types_test.go
Original file line number Diff line number Diff line change
@@ -79,7 +79,7 @@ func Test_initStardogClientFromRef(t *testing.T) {

base64.StdEncoding.EncodeToString([]byte(username))

_, err = rc.initStardogClientFromRef(fakeKubeClient, stardogInstanceRef)
_, _, err = rc.initStardogClientFromRef(fakeKubeClient, stardogInstanceRef)

assert.Equal(t, tt.err, err)
})
@@ -185,12 +185,28 @@ func createPartialSecret(namespace, name, username, password string) *v1.Secret
}

func createKubeFakeClient(initObjs ...runtime.Object) (client.Client, error) {
s := scheme.Scheme
err := stardogv1alpha1.AddToScheme(scheme.Scheme)
err = v1beta1.AddToScheme(scheme.Scheme)
if err != nil {
return nil, err
}
return fake.NewClientBuilder().
WithScheme(scheme.Scheme).
WithScheme(s).
WithRuntimeObjects(initObjs...).
Build(), nil
}

func createKubeFakeClientWithSub(initObjs ...client.Object) (client.Client, error) {
s := scheme.Scheme
err := stardogv1alpha1.AddToScheme(scheme.Scheme)
err = v1beta1.AddToScheme(scheme.Scheme)
if err != nil {
return nil, err
}
return fake.NewClientBuilder().
WithScheme(s).
WithObjects(initObjs...).
WithStatusSubresource(&v1beta1.Organization{}, &v1beta1.Database{}).
Build(), nil
}
4 changes: 4 additions & 0 deletions controllers/stardoginstance_controller.go
Original file line number Diff line number Diff line change
@@ -221,6 +221,10 @@ func (r *StardogInstanceReconciler) validateConnection(sir *StardogInstanceRecon
spec := sir.resource.Spec
credentials := spec.AdminCredentials

if spec.Disabled {
return nil
}

r.Log.V(1).Info("retrieving admin credentials from Secret", "secret", credentials.Namespace+"/"+credentials.SecretRef)
auth, err := rc.initStardogClient(r.Client, *sir.resource)
if err != nil {
3 changes: 2 additions & 1 deletion controllers/stardoginstance_controller_test.go
Original file line number Diff line number Diff line change
@@ -695,7 +695,7 @@ func Test_ReconcileStardogInstance(t *testing.T) {
func createStardogInstance(namespace, name, secretName, serverURL string) *v1alpha1.StardogInstance {
return &v1alpha1.StardogInstance{
TypeMeta: metav1.TypeMeta{Kind: "StardogInstance", APIVersion: "v1alpha1"},
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace, Finalizers: make([]string, 0)},
Spec: v1alpha1.StardogInstanceSpec{
AdminCredentials: v1alpha1.StardogUserCredentialsSpec{
Namespace: namespace,
@@ -710,6 +710,7 @@ func createDeletedStardogInstance(namespace, name, secretName, serverURL string)
stardogInstace := createStardogInstance(namespace, name, secretName, serverURL)
newTime := metav1.NewTime(time.Now())
stardogInstace.SetDeletionTimestamp(&newTime)
stardogInstace.SetFinalizers([]string{"finalizer"})
return stardogInstace
}

20 changes: 14 additions & 6 deletions controllers/stardogrole_controller.go
Original file line number Diff line number Diff line change
@@ -117,16 +117,20 @@ func (r *StardogRoleReconciler) syncRole(srr *StardogRoleReconciliation) error {
spec := srr.resource.Spec
namespace := srr.reconciliationContext.namespace
roleName := spec.RoleName
instance := v1beta1.NewStardogInstanceRef(spec.StardogInstanceRef, namespace)
if roleName == "" {
roleName = srr.resource.Name
}

r.Log.V(1).Info("init Stardog Client from ", "ref", spec.StardogInstanceRef)
auth, err := srr.reconciliationContext.initStardogClientFromRef(r.Client, v1beta1.NewStardogInstanceRef(spec.StardogInstanceRef, namespace))
auth, disabled, err := srr.reconciliationContext.initStardogClientFromRef(r.Client, instance)
if err != nil {
return err
return fmt.Errorf("cannot initialize stardog client: %v", err)
}
if disabled {
r.Log.Info("skipping resource from reconciliation", "instance", instance.Name, "resource", srr.resource.Name)
return nil
}

stardogClient := srr.reconciliationContext.stardogClient

r.Log.Info("synchronizing role", "role", roleName)
@@ -251,11 +255,15 @@ func (r *StardogRoleReconciler) deleteStardogRole(srr *StardogRoleReconciliation
func (r *StardogRoleReconciler) finalize(srr *StardogRoleReconciliation) error {
spec := srr.resource.Spec
namespace := srr.reconciliationContext.namespace

instance := v1beta1.NewStardogInstanceRef(spec.StardogInstanceRef, namespace)
r.Log.V(1).Info("setup Stardog Client from ", "ref", spec.StardogInstanceRef)
auth, err := srr.reconciliationContext.initStardogClientFromRef(r.Client, v1beta1.NewStardogInstanceRef(spec.StardogInstanceRef, namespace))
auth, disabled, err := srr.reconciliationContext.initStardogClientFromRef(r.Client, instance)
if err != nil {
return err
return fmt.Errorf("cannot initialize stardog client: %v", err)
}
if disabled {
r.Log.Info("skipping resource from reconciliation", "instance", instance.Name, "resource", srr.resource.Name)
return nil
}

stardogClient := srr.reconciliationContext.stardogClient
1 change: 1 addition & 0 deletions controllers/stardogrole_controller_test.go
Original file line number Diff line number Diff line change
@@ -910,6 +910,7 @@ func createDeletedStardogRole(namespace, stardogRoleName, stardogInstanceRef str
stardogRole := createStardogRole(namespace, stardogRoleName, stardogInstanceRef, permissions)
newTime := metav1.NewTime(time.Now())
stardogRole.SetDeletionTimestamp(&newTime)
stardogRole.SetFinalizers([]string{"finalizer"})
return stardogRole
}

18 changes: 14 additions & 4 deletions controllers/stardoguser_controller.go
Original file line number Diff line number Diff line change
@@ -137,11 +137,16 @@ func (r *StardogUserReconciler) finalize(sur *StardogUserReconciliation) error {
rc := sur.reconciliationContext
spec := sur.resource.Spec
namespace := rc.namespace
instance := v1beta1.NewStardogInstanceRef(spec.StardogInstanceRef, namespace)

r.Log.V(1).Info("setup Stardog Client from ", "ref", spec.StardogInstanceRef)
auth, err := rc.initStardogClientFromRef(r.Client, v1beta1.NewStardogInstanceRef(spec.StardogInstanceRef, namespace))
auth, disabled, err := rc.initStardogClientFromRef(r.Client, instance)
if err != nil {
return err
return fmt.Errorf("cannot initialize stardog client: %v", err)
}
if disabled {
r.Log.Info("skipping resource from reconciliation", "instance", instance.Name, "resource", sur.resource.Name)
return nil
}

_, err = rc.stardogClient.Users.RemoveUser(model_users.NewRemoveUserParams().WithUser(sur.resource.Name), auth)
@@ -167,11 +172,16 @@ func (r *StardogUserReconciler) syncUser(sur *StardogUserReconciliation) error {
spec := sur.resource.Spec
userCredentials := spec.Credentials
namespace := rc.namespace
instance := v1beta1.NewStardogInstanceRef(spec.StardogInstanceRef, namespace)

r.Log.V(1).Info("init Stardog Client from ", "ref", spec.StardogInstanceRef)
auth, err := rc.initStardogClientFromRef(r.Client, v1beta1.NewStardogInstanceRef(spec.StardogInstanceRef, namespace))
auth, disabled, err := rc.initStardogClientFromRef(r.Client, instance)
if err != nil {
return err
return fmt.Errorf("cannot initialize stardog client: %v", err)
}
if disabled {
r.Log.Info("skipping resource from reconciliation", "instance", instance.Name, "resource", sur.resource.Name)
return nil
}

r.Log.V(1).Info("retrieving user credentials from Secret", "secret", userCredentials.Namespace+"/"+userCredentials.SecretRef)
1 change: 1 addition & 0 deletions controllers/stardoguser_controller_test.go
Original file line number Diff line number Diff line change
@@ -743,6 +743,7 @@ func createDeletedStardogUser(namespace, stardogUserName, stardogInstanceRef, se
user := createStardogUser(namespace, stardogUserName, stardogInstanceRef, secretRef, roles)
newTime := metav1.NewTime(time.Now())
user.SetDeletionTimestamp(&newTime)
user.SetFinalizers([]string{"finalizer"})
return user
}

4 changes: 4 additions & 0 deletions controllers/util.go
Original file line number Diff line number Diff line change
@@ -21,6 +21,10 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type StardogResponse interface {
IsCode(code int) bool
}

var (
ReconFreqErr = time.Second * 30
ReconFreq = time.Duration(0)
86 changes: 44 additions & 42 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,56 +1,57 @@
module github.com/vshn/stardog-userrole-operator

go 1.20
go 1.22

require (
github.com/go-logr/logr v1.2.3
github.com/go-logr/logr v1.4.1
github.com/go-openapi/errors v0.20.4
github.com/go-openapi/runtime v0.26.0
github.com/go-openapi/strfmt v0.21.7
github.com/go-openapi/swag v0.22.4
github.com/go-openapi/validate v0.22.1
github.com/golang/mock v1.6.0
github.com/onsi/ginkgo/v2 v2.8.1
github.com/onsi/gomega v1.26.0
github.com/onsi/ginkgo/v2 v2.14.0
github.com/onsi/gomega v1.30.0
github.com/sethvargo/go-password v0.2.0
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
k8s.io/api v0.26.1
k8s.io/apimachinery v0.26.1
k8s.io/client-go v0.26.1
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448
sigs.k8s.io/controller-runtime v0.14.4
k8s.io/api v0.29.0
k8s.io/apimachinery v0.29.0
k8s.io/client-go v0.29.0
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
sigs.k8s.io/controller-runtime v0.17.2
)

require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/evanphx/json-patch/v5 v5.8.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.2.3 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -59,34 +60,35 @@ require (
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/prometheus/client_golang v1.18.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.mongodb.org/mongo-driver v1.11.3 // indirect
go.opentelemetry.io/otel v1.14.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/term v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
go.opentelemetry.io/otel v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.19.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/oauth2 v0.12.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
golang.org/x/tools v0.16.1 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.26.1 // indirect
k8s.io/component-base v0.26.1 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
k8s.io/apiextensions-apiserver v0.29.0 // indirect
k8s.io/component-base v0.29.0 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
577 changes: 94 additions & 483 deletions go.sum

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
@@ -2,18 +2,18 @@ package main

import (
"flag"
"os"

"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"os"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

stardogv1alpha1 "github.com/vshn/stardog-userrole-operator/api/v1alpha1"
stardogv1beta1 "github.com/vshn/stardog-userrole-operator/api/v1beta1"
"github.com/vshn/stardog-userrole-operator/controllers"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
// +kubebuilder:scaffold:imports
)

@@ -42,12 +42,14 @@ func main() {
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
LeaderElection: enableLeaderElection,
LeaderElectionID: "2b6e0679.vshn.ch",
Scheme: scheme,
Metrics: metricsserver.Options{
BindAddress: metricsAddr,
},
LeaderElection: enableLeaderElection,
LeaderElectionID: "2b6e0679.vshn.ch",
})
//handle := handler.EnqueueRequestsFromMapFunc()
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
151 changes: 151 additions & 0 deletions stardogrest/client/users/get_user_parameters.go
256 changes: 256 additions & 0 deletions stardogrest/client/users/get_user_responses.go
40 changes: 40 additions & 0 deletions stardogrest/client/users/users_client.go
20 changes: 20 additions & 0 deletions stardogrest/mocks/mock_client.go
8,636 changes: 8,636 additions & 0 deletions stardogrest/openapi.yaml

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions stardogrest/stardog_swagger.yaml
Original file line number Diff line number Diff line change
@@ -371,6 +371,30 @@ paths:
description: unexpected error
schema:
"$ref": "#/definitions/Error"
get:
summary: Get a user.
operationId: getUser
tags:
- users
parameters:
- name: user
in: path
required: true
description: The username of the user to get
type: string
responses:
'200':
description: A user if it exists
schema:
"$ref": "#/definitions/Users"
'404':
description: User does not exist
schema:
"$ref": "#/definitions/NotExists"
default:
description: unexpected error
schema:
"$ref": "#/definitions/Error"
"/admin/roles":
get:
summary: List all roles.

0 comments on commit ecf682b

Please sign in to comment.