1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-15 17:51:03 +00:00

[Feature] [ML] SA Management (#1525)

This commit is contained in:
Adam Janikowski 2023-12-06 09:41:23 +01:00 committed by GitHub
parent a7bd9cb54e
commit f1038721b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1240 additions and 18 deletions

View file

@ -34,10 +34,23 @@ rules:
- "get" - "get"
- "list" - "list"
- "watch" - "watch"
- apiGroups:
- "rbac.authorization.k8s.io"
resources:
- "roles"
- "rolebindings"
verbs: ["*"]
- apiGroups:
- "batch"
resources:
- "cronjobs"
- "jobs"
verbs: ["*"]
- apiGroups: [""] - apiGroups: [""]
resources: resources:
- "secrets"
- "pods" - "pods"
- "secrets"
- "serviceaccounts"
verbs: ["*"] verbs: ["*"]
{{- end }} {{- end }}
{{- end }} {{- end }}

View file

@ -76,7 +76,7 @@ PullSecrets define Secrets used to pull Image from registry
### .spec.storage.name ### .spec.storage.name
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L32)</sup> Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L46)</sup>
Name of the object Name of the object
@ -84,7 +84,7 @@ Name of the object
### .spec.storage.namespace ### .spec.storage.namespace
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L35)</sup> Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L49)</sup>
Namespace of the object. Should default to the namespace of the parent object Namespace of the object. Should default to the namespace of the parent object
@ -92,7 +92,7 @@ Namespace of the object. Should default to the namespace of the parent object
### .spec.storage.uid ### .spec.storage.uid
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L38)</sup> Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L52)</sup>
UID keeps the information about object UID UID keeps the information about object UID
@ -100,7 +100,7 @@ UID keeps the information about object UID
### .status.conditions ### .status.conditions
Type: `api.Conditions` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/ml/v1alpha1/extension_status.go#L28)</sup> Type: `api.Conditions` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/ml/v1alpha1/extension_status.go#L31)</sup>
Conditions specific to the entire extension Conditions specific to the entire extension
@ -124,7 +124,7 @@ ArangoPipeDatabase define Database name to be used as MetadataService Backend
### .status.metadataService.secret.name ### .status.metadataService.secret.name
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L32)</sup> Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L46)</sup>
Name of the object Name of the object
@ -132,7 +132,7 @@ Name of the object
### .status.metadataService.secret.namespace ### .status.metadataService.secret.namespace
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L35)</sup> Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L49)</sup>
Namespace of the object. Should default to the namespace of the parent object Namespace of the object. Should default to the namespace of the parent object
@ -140,7 +140,127 @@ Namespace of the object. Should default to the namespace of the parent object
### .status.metadataService.secret.uid ### .status.metadataService.secret.uid
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L38)</sup> Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L52)</sup>
UID keeps the information about object UID
***
### .status.serviceAccount.cluster.binding.name
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L46)</sup>
Name of the object
***
### .status.serviceAccount.cluster.binding.namespace
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L49)</sup>
Namespace of the object. Should default to the namespace of the parent object
***
### .status.serviceAccount.cluster.binding.uid
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L52)</sup>
UID keeps the information about object UID
***
### .status.serviceAccount.cluster.role.name
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L46)</sup>
Name of the object
***
### .status.serviceAccount.cluster.role.namespace
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L49)</sup>
Namespace of the object. Should default to the namespace of the parent object
***
### .status.serviceAccount.cluster.role.uid
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L52)</sup>
UID keeps the information about object UID
***
### .status.serviceAccount.name
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L46)</sup>
Name of the object
***
### .status.serviceAccount.namespace
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L49)</sup>
Namespace of the object. Should default to the namespace of the parent object
***
### .status.serviceAccount.namespaced.binding.name
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L46)</sup>
Name of the object
***
### .status.serviceAccount.namespaced.binding.namespace
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L49)</sup>
Namespace of the object. Should default to the namespace of the parent object
***
### .status.serviceAccount.namespaced.binding.uid
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L52)</sup>
UID keeps the information about object UID
***
### .status.serviceAccount.namespaced.role.name
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L46)</sup>
Name of the object
***
### .status.serviceAccount.namespaced.role.namespace
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L49)</sup>
Namespace of the object. Should default to the namespace of the parent object
***
### .status.serviceAccount.namespaced.role.uid
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L52)</sup>
UID keeps the information about object UID
***
### .status.serviceAccount.uid
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L52)</sup>
UID keeps the information about object UID UID keeps the information about object UID

View file

@ -14,7 +14,7 @@ Default Value: `false`
### .spec.backend.s3.caSecret.name ### .spec.backend.s3.caSecret.name
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L32)</sup> Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L46)</sup>
Name of the object Name of the object
@ -22,7 +22,7 @@ Name of the object
### .spec.backend.s3.caSecret.namespace ### .spec.backend.s3.caSecret.namespace
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L35)</sup> Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L49)</sup>
Namespace of the object. Should default to the namespace of the parent object Namespace of the object. Should default to the namespace of the parent object
@ -30,7 +30,7 @@ Namespace of the object. Should default to the namespace of the parent object
### .spec.backend.s3.caSecret.uid ### .spec.backend.s3.caSecret.uid
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L38)</sup> Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L52)</sup>
UID keeps the information about object UID UID keeps the information about object UID
@ -38,7 +38,7 @@ UID keeps the information about object UID
### .spec.backend.s3.credentialsSecret.name ### .spec.backend.s3.credentialsSecret.name
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L32)</sup> Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L46)</sup>
Name of the object Name of the object
@ -46,7 +46,7 @@ Name of the object
### .spec.backend.s3.credentialsSecret.namespace ### .spec.backend.s3.credentialsSecret.namespace
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L35)</sup> Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L49)</sup>
Namespace of the object. Should default to the namespace of the parent object Namespace of the object. Should default to the namespace of the parent object
@ -54,7 +54,7 @@ Namespace of the object. Should default to the namespace of the parent object
### .spec.backend.s3.credentialsSecret.uid ### .spec.backend.s3.credentialsSecret.uid
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L38)</sup> Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L52)</sup>
UID keeps the information about object UID UID keeps the information about object UID

View file

@ -27,5 +27,6 @@ const (
ExtensionDeploymentFoundCondition api.ConditionType = "DeploymentFound" ExtensionDeploymentFoundCondition api.ConditionType = "DeploymentFound"
ExtensionBootstrapCompletedCondition api.ConditionType = "BootstrapCompleted" ExtensionBootstrapCompletedCondition api.ConditionType = "BootstrapCompleted"
ExtensionMetadataServiceValidCondition api.ConditionType = "MetadataServiceValid" ExtensionMetadataServiceValidCondition api.ConditionType = "MetadataServiceValid"
ExtensionServiceAccountReadyCondition api.ConditionType = "ServiceAccountReady"
LicenseValidCondition api.ConditionType = "LicenseValid" LicenseValidCondition api.ConditionType = "LicenseValid"
) )

View file

@ -20,7 +20,10 @@
package v1alpha1 package v1alpha1
import api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1"
)
type ArangoMLExtensionStatus struct { type ArangoMLExtensionStatus struct {
// Conditions specific to the entire extension // Conditions specific to the entire extension
@ -29,4 +32,7 @@ type ArangoMLExtensionStatus struct {
// MetadataService keeps the MetadataService configuration // MetadataService keeps the MetadataService configuration
MetadataService *ArangoMLExtensionStatusMetadataService `json:"metadataService,omitempty"` MetadataService *ArangoMLExtensionStatusMetadataService `json:"metadataService,omitempty"`
// ServiceAccount keeps the information about ServiceAccount
ServiceAccount *shared.ServiceAccount `json:"serviceAccount,omitempty"`
} }

View file

@ -418,6 +418,11 @@ func (in *ArangoMLExtensionStatus) DeepCopyInto(out *ArangoMLExtensionStatus) {
*out = new(ArangoMLExtensionStatusMetadataService) *out = new(ArangoMLExtensionStatusMetadataService)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.ServiceAccount != nil {
in, out := &in.ServiceAccount, &out.ServiceAccount
*out = new(sharedv1.ServiceAccount)
(*in).DeepCopyInto(*out)
}
return return
} }

View file

@ -25,8 +25,22 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"github.com/arangodb/kube-arangodb/pkg/apis/shared" "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util"
) )
func NewObject(object meta.Object) Object {
var n Object
n.Name = object.GetName()
n.UID = util.NewType(object.GetUID())
if ns := object.GetNamespace(); ns != "" {
n.Namespace = util.NewType(ns)
}
return n
}
type Object struct { type Object struct {
// Name of the object // Name of the object
Name string `json:"name"` Name string `json:"name"`
@ -71,6 +85,30 @@ func (o *Object) GetUID() types.UID {
return "" return ""
} }
func (o *Object) Equals(obj meta.Object) bool {
if o == nil {
return false
}
if o.Name != obj.GetName() {
return false
}
if n := o.Namespace; n != nil {
if *n != obj.GetNamespace() {
return false
}
}
if n := o.UID; n != nil {
if *n != obj.GetUID() {
return false
}
}
return true
}
func (o *Object) Validate() error { func (o *Object) Validate() error {
if o == nil { if o == nil {
o = &Object{} o = &Object{}

View file

@ -0,0 +1,40 @@
//
// DISCLAIMER
//
// Copyright 2023 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package v1
type ServiceAccount struct {
// Object keeps the reference to the ServiceAccount
*Object `json:",inline"`
// Namespaced keeps the reference to core.Role objects
Namespaced *ServiceAccountRole `json:"namespaced,omitempty"`
// Cluster keeps the reference to core.ClusterRole objects
Cluster *ServiceAccountRole `json:"cluster,omitempty"`
}
type ServiceAccountRole struct {
// Role keeps the reference to the Kubernetes Role
Role *Object `json:"role,omitempty"`
// Binding keeps the reference to the Kubernetes Binding
Binding *Object `json:"binding,omitempty"`
}

View file

@ -127,3 +127,60 @@ func (in *Resources) DeepCopy() *Resources {
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceAccount) DeepCopyInto(out *ServiceAccount) {
*out = *in
if in.Object != nil {
in, out := &in.Object, &out.Object
*out = new(Object)
(*in).DeepCopyInto(*out)
}
if in.Namespaced != nil {
in, out := &in.Namespaced, &out.Namespaced
*out = new(ServiceAccountRole)
(*in).DeepCopyInto(*out)
}
if in.Cluster != nil {
in, out := &in.Cluster, &out.Cluster
*out = new(ServiceAccountRole)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccount.
func (in *ServiceAccount) DeepCopy() *ServiceAccount {
if in == nil {
return nil
}
out := new(ServiceAccount)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceAccountRole) DeepCopyInto(out *ServiceAccountRole) {
*out = *in
if in.Role != nil {
in, out := &in.Role, &out.Role
*out = new(Object)
(*in).DeepCopyInto(*out)
}
if in.Binding != nil {
in, out := &in.Binding, &out.Binding
*out = new(Object)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRole.
func (in *ServiceAccountRole) DeepCopy() *ServiceAccountRole {
if in == nil {
return nil
}
out := new(ServiceAccountRole)
in.DeepCopyInto(out)
return out
}

View file

@ -24,6 +24,10 @@ import (
"context" "context"
"time" "time"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/arangodb/kube-arangodb/pkg/deployment/patch"
"github.com/arangodb/kube-arangodb/pkg/util/globals" "github.com/arangodb/kube-arangodb/pkg/util/globals"
) )
@ -48,3 +52,25 @@ func WithContextTimeoutP2A2[P1, P2, A1, A2 interface{}](ctx context.Context, tim
return f(nCtx, a1, a2) return f(nCtx, a1, a2)
} }
type PatchInterface[P1 meta.Object] interface {
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts meta.PatchOptions, subresources ...string) (P1, error)
}
func WithKubernetesPatch[P1 meta.Object](ctx context.Context, obj string, client PatchInterface[P1], p ...patch.Item) (P1, error) {
if len(p) == 0 {
return Default[P1](), nil
}
parser := patch.Patch(p)
data, err := parser.Marshal()
if err != nil {
return Default[P1](), err
}
nCtx, c := context.WithTimeout(ctx, globals.GetGlobals().Timeouts().Kubernetes().Get())
defer c()
return client.Patch(nCtx, obj, types.JSONPatchType, data, meta.PatchOptions{})
}

View file

@ -0,0 +1,394 @@
//
// DISCLAIMER
//
// Copyright 2023 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package helpers
import (
"context"
"fmt"
"strings"
"github.com/dchest/uniuri"
core "k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/equality"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1"
"github.com/arangodb/kube-arangodb/pkg/deployment/patch"
operator "github.com/arangodb/kube-arangodb/pkg/operatorV2"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors"
)
func EnsureServiceAccount(ctx context.Context, client kubernetes.Interface, owner meta.OwnerReference, obj *sharedApi.ServiceAccount, name, namespace string, role, clusterRole []rbac.PolicyRule) (bool, error) {
if obj == nil {
return false, errors.Newf("Object reference cannot be nil")
}
// Check if secret exists
if obj.Object != nil {
if sa, err := util.WithKubernetesContextTimeoutP2A2(ctx, client.CoreV1().ServiceAccounts(namespace).Get, obj.Object.GetName(), meta.GetOptions{}); err != nil {
if !kerrors.IsNotFound(err) {
return false, err
}
obj.Object = nil
return true, operator.Reconcile("SA is missing")
} else {
if !obj.Object.Equals(sa) {
// Invalid object
if err := util.WithKubernetesContextTimeoutP1A2(ctx, client.CoreV1().ServiceAccounts(namespace).Delete, name, meta.DeleteOptions{
Preconditions: meta.NewUIDPreconditions(string(obj.Object.GetUID())),
}); err != nil {
if !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) {
return false, err
}
}
obj.Object = nil
return true, operator.Reconcile("Removing SA")
}
}
}
if obj.Object == nil {
if sa, err := util.WithKubernetesContextTimeoutP2A2(ctx, client.CoreV1().ServiceAccounts(namespace).Create, &core.ServiceAccount{
ObjectMeta: meta.ObjectMeta{
OwnerReferences: []meta.OwnerReference{owner},
Name: fmt.Sprintf("%s-%s", name, strings.ToLower(uniuri.NewLen(6))),
Namespace: namespace,
},
}, meta.CreateOptions{}); err != nil {
return false, err
} else {
obj.Object = util.NewType(sharedApi.NewObject(sa))
return true, operator.Reconcile("Created SA")
}
}
// ROLE
if len(role) == 0 {
// Ensure role and binding is missing
if obj.Namespaced != nil {
if obj.Namespaced.Binding != nil {
// Remove binding
if err := util.WithKubernetesContextTimeoutP1A2(ctx, client.RbacV1().RoleBindings(namespace).Delete, obj.Namespaced.Binding.GetName(), meta.DeleteOptions{
Preconditions: meta.NewUIDPreconditions(string(obj.Namespaced.Binding.GetUID())),
}); err != nil {
if !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) {
return false, err
}
}
obj.Namespaced.Binding = nil
return true, operator.Reconcile("Removing RoleBinding")
}
if obj.Namespaced.Role != nil {
// Remove binding
if err := util.WithKubernetesContextTimeoutP1A2(ctx, client.RbacV1().Roles(namespace).Delete, obj.Namespaced.Role.GetName(), meta.DeleteOptions{
Preconditions: meta.NewUIDPreconditions(string(obj.Namespaced.Role.GetUID())),
}); err != nil {
if !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) {
return false, err
}
}
obj.Namespaced.Role = nil
return true, operator.Reconcile("Removing Role")
}
obj.Namespaced = nil
return true, operator.Reconcile("Removing Namespaced Handler")
}
} else {
// Create if required
if obj.Namespaced == nil || obj.Namespaced.Role == nil {
if r, err := util.WithKubernetesContextTimeoutP2A2(ctx, client.RbacV1().Roles(namespace).Create, &rbac.Role{
ObjectMeta: meta.ObjectMeta{
OwnerReferences: []meta.OwnerReference{owner},
Name: fmt.Sprintf("%s-%s", name, strings.ToLower(uniuri.NewLen(6))),
Namespace: namespace,
},
Rules: role,
}, meta.CreateOptions{}); err != nil {
return false, err
} else {
if obj.Namespaced == nil {
obj.Namespaced = &sharedApi.ServiceAccountRole{}
}
obj.Namespaced.Role = util.NewType(sharedApi.NewObject(r))
return true, operator.Reconcile("Created Role")
}
}
if obj.Namespaced == nil || obj.Namespaced.Binding == nil {
if r, err := util.WithKubernetesContextTimeoutP2A2(ctx, client.RbacV1().RoleBindings(namespace).Create, &rbac.RoleBinding{
ObjectMeta: meta.ObjectMeta{
OwnerReferences: []meta.OwnerReference{owner},
Name: fmt.Sprintf("%s-%s", name, strings.ToLower(uniuri.NewLen(6))),
Namespace: namespace,
},
RoleRef: rbac.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: obj.Namespaced.Role.GetName(),
},
Subjects: []rbac.Subject{
{
Kind: "ServiceAccount",
APIGroup: "",
Name: obj.Object.GetName(),
Namespace: namespace,
},
},
}, meta.CreateOptions{}); err != nil {
return false, err
} else {
if obj.Namespaced == nil {
obj.Namespaced = &sharedApi.ServiceAccountRole{}
}
obj.Namespaced.Binding = util.NewType(sharedApi.NewObject(r))
return true, operator.Reconcile("Created RoleBinding")
}
}
// Both object are nil, lets validate aspects
if r, err := util.WithKubernetesContextTimeoutP2A2(ctx, client.RbacV1().Roles(namespace).Get, obj.Namespaced.Role.GetName(), meta.GetOptions{}); err != nil {
if !kerrors.IsNotFound(err) {
return false, err
}
obj.Namespaced.Role = nil
return true, operator.Reconcile("Missing Role")
} else {
if !obj.Namespaced.Role.Equals(r) {
if err := util.WithKubernetesContextTimeoutP1A2(ctx, client.RbacV1().Roles(namespace).Delete, obj.Namespaced.Role.GetName(), meta.DeleteOptions{
Preconditions: meta.NewUIDPreconditions(string(obj.Namespaced.Role.GetUID())),
}); err != nil {
if !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) {
return false, err
}
}
obj.Namespaced.Role = nil
return true, operator.Reconcile("Recreate Role")
}
if !equality.Semantic.DeepEqual(r.Rules, role) {
// There is change in the roles
if _, err := util.WithKubernetesPatch[*rbac.Role](ctx, obj.Namespaced.Role.GetName(), client.RbacV1().Roles(namespace), patch.ItemReplace(patch.NewPath("rules"), role)); err != nil {
if !kerrors.IsNotFound(err) {
return false, err
}
}
return false, operator.Reconcile("Refresh Role")
}
}
if r, err := util.WithKubernetesContextTimeoutP2A2(ctx, client.RbacV1().RoleBindings(namespace).Get, obj.Namespaced.Binding.GetName(), meta.GetOptions{}); err != nil {
if !kerrors.IsNotFound(err) {
return false, err
}
obj.Namespaced.Role = nil
return true, operator.Reconcile("Missing RoleBinding")
} else {
if !obj.Namespaced.Binding.Equals(r) {
if err := util.WithKubernetesContextTimeoutP1A2(ctx, client.RbacV1().RoleBindings(namespace).Delete, obj.Namespaced.Binding.GetName(), meta.DeleteOptions{
Preconditions: meta.NewUIDPreconditions(string(obj.Namespaced.Role.GetUID())),
}); err != nil {
if !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) {
return false, err
}
}
obj.Namespaced.Role = nil
return true, operator.Reconcile("Recreate RoleBinding")
}
}
}
// CLUSTER ROLE
if len(clusterRole) == 0 {
// Ensure role and binding is missing
if obj.Cluster != nil {
if obj.Cluster.Binding != nil {
// Remove binding
if err := util.WithKubernetesContextTimeoutP1A2(ctx, client.RbacV1().ClusterRoleBindings().Delete, obj.Cluster.Binding.GetName(), meta.DeleteOptions{
Preconditions: meta.NewUIDPreconditions(string(obj.Cluster.Binding.GetUID())),
}); err != nil {
if !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) {
return false, err
}
}
obj.Cluster.Binding = nil
return true, operator.Reconcile("Removing ClusterRoleBinding")
}
if obj.Cluster.Role != nil {
// Remove binding
if err := util.WithKubernetesContextTimeoutP1A2(ctx, client.RbacV1().ClusterRoles().Delete, obj.Cluster.Role.GetName(), meta.DeleteOptions{
Preconditions: meta.NewUIDPreconditions(string(obj.Cluster.Role.GetUID())),
}); err != nil {
if !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) {
return false, err
}
}
obj.Cluster.Role = nil
return true, operator.Reconcile("Removing ClusterRole")
}
obj.Cluster = nil
return true, operator.Reconcile("Removing Cluster Handler")
}
} else {
// Create if required
if obj.Cluster == nil || obj.Cluster.Role == nil {
if r, err := util.WithKubernetesContextTimeoutP2A2(ctx, client.RbacV1().ClusterRoles().Create, &rbac.ClusterRole{
ObjectMeta: meta.ObjectMeta{
OwnerReferences: []meta.OwnerReference{owner},
Name: fmt.Sprintf("%s-%s", name, strings.ToLower(uniuri.NewLen(6))),
},
Rules: clusterRole,
}, meta.CreateOptions{}); err != nil {
return false, err
} else {
if obj.Cluster == nil {
obj.Cluster = &sharedApi.ServiceAccountRole{}
}
obj.Cluster.Role = util.NewType(sharedApi.NewObject(r))
return true, operator.Reconcile("Created ClusterRole")
}
}
if obj.Cluster == nil || obj.Cluster.Binding == nil {
if r, err := util.WithKubernetesContextTimeoutP2A2(ctx, client.RbacV1().ClusterRoleBindings().Create, &rbac.ClusterRoleBinding{
ObjectMeta: meta.ObjectMeta{
OwnerReferences: []meta.OwnerReference{owner},
Name: fmt.Sprintf("%s-%s", name, strings.ToLower(uniuri.NewLen(6))),
},
RoleRef: rbac.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: obj.Cluster.Role.GetName(),
},
Subjects: []rbac.Subject{
{
Kind: "ServiceAccount",
APIGroup: "",
Name: obj.Object.GetName(),
Namespace: namespace,
},
},
}, meta.CreateOptions{}); err != nil {
return false, err
} else {
if obj.Cluster == nil {
obj.Cluster = &sharedApi.ServiceAccountRole{}
}
obj.Cluster.Binding = util.NewType(sharedApi.NewObject(r))
return true, operator.Reconcile("Created ClusterRoleBinding")
}
}
// Both object are nil, lets validate aspects
if r, err := util.WithKubernetesContextTimeoutP2A2(ctx, client.RbacV1().ClusterRoles().Get, obj.Cluster.Role.GetName(), meta.GetOptions{}); err != nil {
if !kerrors.IsNotFound(err) {
return false, err
}
obj.Cluster.Role = nil
return true, operator.Reconcile("Missing ClusterRole")
} else {
if !obj.Cluster.Role.Equals(r) {
if err := util.WithKubernetesContextTimeoutP1A2(ctx, client.RbacV1().ClusterRoles().Delete, obj.Cluster.Role.GetName(), meta.DeleteOptions{
Preconditions: meta.NewUIDPreconditions(string(obj.Cluster.Role.GetUID())),
}); err != nil {
if !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) {
return false, err
}
}
obj.Cluster.Role = nil
return true, operator.Reconcile("Recreate ClusterRole")
}
if !equality.Semantic.DeepEqual(r.Rules, clusterRole) {
// There is change in the roles
if _, err := util.WithKubernetesPatch[*rbac.ClusterRole](ctx, obj.Cluster.Role.GetName(), client.RbacV1().ClusterRoles(), patch.ItemReplace(patch.NewPath("rules"), clusterRole)); err != nil {
if !kerrors.IsNotFound(err) {
return false, err
}
}
return false, operator.Reconcile("Refresh ClusterRole")
}
}
if r, err := util.WithKubernetesContextTimeoutP2A2(ctx, client.RbacV1().ClusterRoleBindings().Get, obj.Cluster.Binding.GetName(), meta.GetOptions{}); err != nil {
if !kerrors.IsNotFound(err) {
return false, err
}
obj.Cluster.Role = nil
return true, operator.Reconcile("Missing ClusterRoleBinding")
} else {
if !obj.Cluster.Binding.Equals(r) {
if err := util.WithKubernetesContextTimeoutP1A2(ctx, client.RbacV1().ClusterRoleBindings().Delete, obj.Cluster.Binding.GetName(), meta.DeleteOptions{
Preconditions: meta.NewUIDPreconditions(string(obj.Cluster.Role.GetUID())),
}); err != nil {
if !kerrors.IsNotFound(err) && !kerrors.IsConflict(err) {
return false, err
}
}
obj.Cluster.Role = nil
return true, operator.Reconcile("Recreate ClusterRoleBinding")
}
}
}
return false, nil
}
func AppendServiceAccount(obj *sharedApi.ServiceAccount, spec *core.PodTemplateSpec) {
if obj == nil || obj.Object == nil || spec == nil {
return
}
spec.Spec.ServiceAccountName = obj.Object.GetName()
spec.Spec.AutomountServiceAccountToken = util.NewType(true)
}

View file

@ -0,0 +1,298 @@
//
// DISCLAIMER
//
// Copyright 2023 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package helpers
import (
"context"
"testing"
"github.com/stretchr/testify/require"
rbac "k8s.io/api/rbac/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1"
"github.com/arangodb/kube-arangodb/pkg/util/tests"
)
func Test_ServiceAccount_Roles(t *testing.T) {
k := fake.NewSimpleClientset()
var obj sharedApi.ServiceAccount
t.Run("PreCheck", func(t *testing.T) {
require.Nil(t, obj.Object)
})
t.Run("Create SA without any roles", func(t *testing.T) {
require.NoError(t, tests.HandleFunc(func(ctx context.Context) (bool, error) {
return EnsureServiceAccount(ctx, k, meta.OwnerReference{}, &obj, "test", tests.FakeNamespace, nil, nil)
}))
require.NotNil(t, obj.Object)
require.Nil(t, obj.Namespaced)
require.Nil(t, obj.Cluster)
})
t.Run("Create SA with roles", func(t *testing.T) {
require.NoError(t, tests.HandleFunc(func(ctx context.Context) (bool, error) {
return EnsureServiceAccount(ctx, k, meta.OwnerReference{}, &obj, "test", tests.FakeNamespace, []rbac.PolicyRule{
{
Resources: []string{"*"},
},
}, nil)
}))
require.NotNil(t, obj.Object)
require.NotNil(t, obj.Namespaced)
require.NotNil(t, obj.Namespaced.Binding)
require.NotNil(t, obj.Namespaced.Role)
require.Nil(t, obj.Cluster)
sa := tests.NewMetaObject[*rbac.Role](t, tests.FakeNamespace, obj.Namespaced.Role.GetName())
tests.RefreshObjects(t, k, nil, &sa)
require.Len(t, sa.Rules, 1)
require.Len(t, sa.Rules[0].Resources, 1)
require.Equal(t, sa.Rules[0].Resources[0], "*")
})
t.Run("Create SA with updated roles", func(t *testing.T) {
require.NoError(t, tests.HandleFunc(func(ctx context.Context) (bool, error) {
return EnsureServiceAccount(ctx, k, meta.OwnerReference{}, &obj, "test", tests.FakeNamespace, []rbac.PolicyRule{
{
Resources: []string{"DATA"},
},
}, nil)
}))
require.NotNil(t, obj.Object)
require.NotNil(t, obj.Namespaced)
require.NotNil(t, obj.Namespaced.Binding)
require.NotNil(t, obj.Namespaced.Role)
require.Nil(t, obj.Cluster)
sa := tests.NewMetaObject[*rbac.Role](t, tests.FakeNamespace, obj.Namespaced.Role.GetName())
tests.RefreshObjects(t, k, nil, &sa)
require.Len(t, sa.Rules, 1)
require.Len(t, sa.Rules[0].Resources, 1)
require.Equal(t, sa.Rules[0].Resources[0], "DATA")
})
t.Run("Create SA with multiple roles", func(t *testing.T) {
require.NoError(t, tests.HandleFunc(func(ctx context.Context) (bool, error) {
return EnsureServiceAccount(ctx, k, meta.OwnerReference{}, &obj, "test", tests.FakeNamespace, []rbac.PolicyRule{
{
Resources: []string{"DATA"},
},
{
Resources: []string{"*"},
},
}, nil)
}))
require.NotNil(t, obj.Object)
require.NotNil(t, obj.Namespaced)
require.NotNil(t, obj.Namespaced.Binding)
require.NotNil(t, obj.Namespaced.Role)
require.Nil(t, obj.Cluster)
sa := tests.NewMetaObject[*rbac.Role](t, tests.FakeNamespace, obj.Namespaced.Role.GetName())
tests.RefreshObjects(t, k, nil, &sa)
require.Len(t, sa.Rules, 2)
require.Len(t, sa.Rules[0].Resources, 1)
require.Equal(t, sa.Rules[0].Resources[0], "DATA")
})
t.Run("Create SA with updated multiple roles", func(t *testing.T) {
require.NoError(t, tests.HandleFunc(func(ctx context.Context) (bool, error) {
return EnsureServiceAccount(ctx, k, meta.OwnerReference{}, &obj, "test", tests.FakeNamespace, []rbac.PolicyRule{
{
Resources: []string{"*"},
},
{
Resources: []string{"DATA"},
},
}, nil)
}))
require.NotNil(t, obj.Object)
require.NotNil(t, obj.Namespaced)
require.NotNil(t, obj.Namespaced.Binding)
require.NotNil(t, obj.Namespaced.Role)
require.Nil(t, obj.Cluster)
sa := tests.NewMetaObject[*rbac.Role](t, tests.FakeNamespace, obj.Namespaced.Role.GetName())
tests.RefreshObjects(t, k, nil, &sa)
require.Len(t, sa.Rules, 2)
require.Len(t, sa.Rules[0].Resources, 1)
require.Equal(t, sa.Rules[0].Resources[0], "*")
})
t.Run("Remove SA Roles", func(t *testing.T) {
require.NoError(t, tests.HandleFunc(func(ctx context.Context) (bool, error) {
return EnsureServiceAccount(ctx, k, meta.OwnerReference{}, &obj, "test", tests.FakeNamespace, nil, nil)
}))
require.NotNil(t, obj.Object)
require.Nil(t, obj.Namespaced)
require.Nil(t, obj.Cluster)
})
}
func Test_ServiceAccount_ClusterRoles(t *testing.T) {
k := fake.NewSimpleClientset()
var obj sharedApi.ServiceAccount
t.Run("PreCheck", func(t *testing.T) {
require.Nil(t, obj.Object)
})
t.Run("Create SA without any roles", func(t *testing.T) {
require.NoError(t, tests.HandleFunc(func(ctx context.Context) (bool, error) {
return EnsureServiceAccount(ctx, k, meta.OwnerReference{}, &obj, "test", tests.FakeNamespace, nil, nil)
}))
require.NotNil(t, obj.Object)
require.Nil(t, obj.Cluster)
require.Nil(t, obj.Namespaced)
})
t.Run("Create SA with roles", func(t *testing.T) {
require.NoError(t, tests.HandleFunc(func(ctx context.Context) (bool, error) {
return EnsureServiceAccount(ctx, k, meta.OwnerReference{}, &obj, "test", tests.FakeNamespace, nil, []rbac.PolicyRule{
{
Resources: []string{"*"},
},
})
}))
require.NotNil(t, obj.Object)
require.NotNil(t, obj.Cluster)
require.NotNil(t, obj.Cluster.Binding)
require.NotNil(t, obj.Cluster.Role)
require.Nil(t, obj.Namespaced)
sa := tests.NewMetaObject[*rbac.ClusterRole](t, tests.FakeNamespace, obj.Cluster.Role.GetName())
tests.RefreshObjects(t, k, nil, &sa)
require.Len(t, sa.Rules, 1)
require.Len(t, sa.Rules[0].Resources, 1)
require.Equal(t, sa.Rules[0].Resources[0], "*")
})
t.Run("Create SA with updated roles", func(t *testing.T) {
require.NoError(t, tests.HandleFunc(func(ctx context.Context) (bool, error) {
return EnsureServiceAccount(ctx, k, meta.OwnerReference{}, &obj, "test", tests.FakeNamespace, nil, []rbac.PolicyRule{
{
Resources: []string{"DATA"},
},
})
}))
require.NotNil(t, obj.Object)
require.NotNil(t, obj.Cluster)
require.NotNil(t, obj.Cluster.Binding)
require.NotNil(t, obj.Cluster.Role)
require.Nil(t, obj.Namespaced)
sa := tests.NewMetaObject[*rbac.ClusterRole](t, tests.FakeNamespace, obj.Cluster.Role.GetName())
tests.RefreshObjects(t, k, nil, &sa)
require.Len(t, sa.Rules, 1)
require.Len(t, sa.Rules[0].Resources, 1)
require.Equal(t, sa.Rules[0].Resources[0], "DATA")
})
t.Run("Create SA with multiple roles", func(t *testing.T) {
require.NoError(t, tests.HandleFunc(func(ctx context.Context) (bool, error) {
return EnsureServiceAccount(ctx, k, meta.OwnerReference{}, &obj, "test", tests.FakeNamespace, nil, []rbac.PolicyRule{
{
Resources: []string{"DATA"},
},
{
Resources: []string{"*"},
},
})
}))
require.NotNil(t, obj.Object)
require.NotNil(t, obj.Cluster)
require.NotNil(t, obj.Cluster.Binding)
require.NotNil(t, obj.Cluster.Role)
require.Nil(t, obj.Namespaced)
sa := tests.NewMetaObject[*rbac.ClusterRole](t, tests.FakeNamespace, obj.Cluster.Role.GetName())
tests.RefreshObjects(t, k, nil, &sa)
require.Len(t, sa.Rules, 2)
require.Len(t, sa.Rules[0].Resources, 1)
require.Equal(t, sa.Rules[0].Resources[0], "DATA")
})
t.Run("Create SA with updated multiple roles", func(t *testing.T) {
require.NoError(t, tests.HandleFunc(func(ctx context.Context) (bool, error) {
return EnsureServiceAccount(ctx, k, meta.OwnerReference{}, &obj, "test", tests.FakeNamespace, nil, []rbac.PolicyRule{
{
Resources: []string{"*"},
},
{
Resources: []string{"DATA"},
},
})
}))
require.NotNil(t, obj.Object)
require.NotNil(t, obj.Cluster)
require.NotNil(t, obj.Cluster.Binding)
require.NotNil(t, obj.Cluster.Role)
require.Nil(t, obj.Namespaced)
sa := tests.NewMetaObject[*rbac.ClusterRole](t, tests.FakeNamespace, obj.Cluster.Role.GetName())
tests.RefreshObjects(t, k, nil, &sa)
require.Len(t, sa.Rules, 2)
require.Len(t, sa.Rules[0].Resources, 1)
require.Equal(t, sa.Rules[0].Resources[0], "*")
})
t.Run("Remove SA Roles", func(t *testing.T) {
require.NoError(t, tests.HandleFunc(func(ctx context.Context) (bool, error) {
return EnsureServiceAccount(ctx, k, meta.OwnerReference{}, &obj, "test", tests.FakeNamespace, nil, nil)
}))
require.NotNil(t, obj.Object)
require.Nil(t, obj.Cluster)
require.Nil(t, obj.Namespaced)
})
}

View file

@ -29,6 +29,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
batch "k8s.io/api/batch/v1" batch "k8s.io/api/batch/v1"
core "k8s.io/api/core/v1" core "k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
@ -46,6 +47,27 @@ import (
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors"
) )
type handleFunc struct {
in func(ctx context.Context) (bool, error)
}
func (h handleFunc) Name() string {
return "mock"
}
func (h handleFunc) Handle(ctx context.Context, item operation.Item) error {
_, err := h.in(ctx)
return err
}
func (h handleFunc) CanBeHandled(item operation.Item) bool {
return true
}
func HandleFunc(in func(ctx context.Context) (bool, error)) error {
return Handle(handleFunc{in: in}, operation.Item{})
}
func Handle(handler operator.Handler, item operation.Item) error { func Handle(handler operator.Handler, item operation.Item) error {
return HandleWithMax(handler, item, 128) return HandleWithMax(handler, item, 128)
} }
@ -80,17 +102,23 @@ func CreateObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe
vl := *v vl := *v
_, err := k8s.BatchV1().Jobs(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{}) _, err := k8s.BatchV1().Jobs(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{})
require.NoError(t, err) require.NoError(t, err)
case **core.Pod:
require.NotNil(t, v)
vl := *v
_, err := k8s.CoreV1().Pods(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{})
require.NoError(t, err)
case **core.Secret: case **core.Secret:
require.NotNil(t, v) require.NotNil(t, v)
vl := *v vl := *v
_, err := k8s.CoreV1().Secrets(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{}) _, err := k8s.CoreV1().Secrets(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{})
require.NoError(t, err) require.NoError(t, err)
case **core.Pod: case **core.ServiceAccount:
require.NotNil(t, v) require.NotNil(t, v)
vl := *v vl := *v
_, err := k8s.CoreV1().Pods(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{}) _, err := k8s.CoreV1().ServiceAccounts(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{})
require.NoError(t, err) require.NoError(t, err)
case **api.ArangoDeployment: case **api.ArangoDeployment:
require.NotNil(t, v) require.NotNil(t, v)
@ -122,6 +150,30 @@ func CreateObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe
vl := *v vl := *v
_, err := arango.MlV1alpha1().ArangoMLStorages(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{}) _, err := arango.MlV1alpha1().ArangoMLStorages(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{})
require.NoError(t, err) require.NoError(t, err)
case **rbac.ClusterRole:
require.NotNil(t, v)
vl := *v
_, err := k8s.RbacV1().ClusterRoles().Create(context.Background(), vl, meta.CreateOptions{})
require.NoError(t, err)
case **rbac.ClusterRoleBinding:
require.NotNil(t, v)
vl := *v
_, err := k8s.RbacV1().ClusterRoleBindings().Create(context.Background(), vl, meta.CreateOptions{})
require.NoError(t, err)
case **rbac.Role:
require.NotNil(t, v)
vl := *v
_, err := k8s.RbacV1().Roles(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{})
require.NoError(t, err)
case **rbac.RoleBinding:
require.NotNil(t, v)
vl := *v
_, err := k8s.RbacV1().RoleBindings(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{})
require.NoError(t, err)
default: default:
require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String())) require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String()))
} }
@ -153,6 +205,12 @@ func UpdateObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe
vl := *v vl := *v
_, err := k8s.CoreV1().Secrets(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{}) _, err := k8s.CoreV1().Secrets(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{})
require.NoError(t, err) require.NoError(t, err)
case **core.ServiceAccount:
require.NotNil(t, v)
vl := *v
_, err := k8s.CoreV1().ServiceAccounts(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{})
require.NoError(t, err)
case **api.ArangoDeployment: case **api.ArangoDeployment:
require.NotNil(t, v) require.NotNil(t, v)
@ -183,6 +241,30 @@ func UpdateObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe
vl := *v vl := *v
_, err := arango.MlV1alpha1().ArangoMLStorages(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{}) _, err := arango.MlV1alpha1().ArangoMLStorages(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{})
require.NoError(t, err) require.NoError(t, err)
case **rbac.ClusterRole:
require.NotNil(t, v)
vl := *v
_, err := k8s.RbacV1().ClusterRoles().Update(context.Background(), vl, meta.UpdateOptions{})
require.NoError(t, err)
case **rbac.ClusterRoleBinding:
require.NotNil(t, v)
vl := *v
_, err := k8s.RbacV1().ClusterRoleBindings().Update(context.Background(), vl, meta.UpdateOptions{})
require.NoError(t, err)
case **rbac.Role:
require.NotNil(t, v)
vl := *v
_, err := k8s.RbacV1().Roles(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{})
require.NoError(t, err)
case **rbac.RoleBinding:
require.NotNil(t, v)
vl := *v
_, err := k8s.RbacV1().RoleBindings(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{})
require.NoError(t, err)
default: default:
require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String())) require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String()))
} }
@ -241,6 +323,21 @@ func RefreshObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientS
} else { } else {
*v = vn *v = vn
} }
case **core.ServiceAccount:
require.NotNil(t, v)
vl := *v
vn, err := k8s.CoreV1().ServiceAccounts(vl.GetNamespace()).Get(context.Background(), vl.GetName(), meta.GetOptions{})
if err != nil {
if kerrors.IsNotFound(err) {
*v = nil
} else {
require.NoError(t, err)
}
} else {
*v = vn
}
case **api.ArangoDeployment: case **api.ArangoDeployment:
require.NotNil(t, v) require.NotNil(t, v)
@ -316,6 +413,66 @@ func RefreshObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientS
} else { } else {
*v = vn *v = vn
} }
case **rbac.ClusterRole:
require.NotNil(t, v)
vl := *v
vn, err := k8s.RbacV1().ClusterRoles().Get(context.Background(), vl.GetName(), meta.GetOptions{})
if err != nil {
if kerrors.IsNotFound(err) {
*v = nil
} else {
require.NoError(t, err)
}
} else {
*v = vn
}
case **rbac.ClusterRoleBinding:
require.NotNil(t, v)
vl := *v
vn, err := k8s.RbacV1().ClusterRoleBindings().Get(context.Background(), vl.GetName(), meta.GetOptions{})
if err != nil {
if kerrors.IsNotFound(err) {
*v = nil
} else {
require.NoError(t, err)
}
} else {
*v = vn
}
case **rbac.Role:
require.NotNil(t, v)
vl := *v
vn, err := k8s.RbacV1().Roles(vl.GetNamespace()).Get(context.Background(), vl.GetName(), meta.GetOptions{})
if err != nil {
if kerrors.IsNotFound(err) {
*v = nil
} else {
require.NoError(t, err)
}
} else {
*v = vn
}
case **rbac.RoleBinding:
require.NotNil(t, v)
vl := *v
vn, err := k8s.RbacV1().RoleBindings(vl.GetNamespace()).Get(context.Background(), vl.GetName(), meta.GetOptions{})
if err != nil {
if kerrors.IsNotFound(err) {
*v = nil
} else {
require.NoError(t, err)
}
} else {
*v = vn
}
default: default:
require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String())) require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String()))
} }
@ -344,6 +501,12 @@ func SetMetaBasedOnType(t *testing.T, object meta.Object) {
v.SetSelfLink(fmt.Sprintf("/api/v1/secrets/%s/%s", v.SetSelfLink(fmt.Sprintf("/api/v1/secrets/%s/%s",
object.GetNamespace(), object.GetNamespace(),
object.GetName())) object.GetName()))
case *core.ServiceAccount:
v.Kind = "ServiceAccount"
v.APIVersion = "v1"
v.SetSelfLink(fmt.Sprintf("/api/v1/serviceaccounts/%s/%s",
object.GetNamespace(),
object.GetName()))
case *api.ArangoDeployment: case *api.ArangoDeployment:
v.Kind = deployment.ArangoDeploymentResourceKind v.Kind = deployment.ArangoDeploymentResourceKind
v.APIVersion = api.SchemeGroupVersion.String() v.APIVersion = api.SchemeGroupVersion.String()
@ -384,6 +547,30 @@ func SetMetaBasedOnType(t *testing.T, object meta.Object) {
ml.ArangoMLStorageResourcePlural, ml.ArangoMLStorageResourcePlural,
object.GetNamespace(), object.GetNamespace(),
object.GetName())) object.GetName()))
case *rbac.ClusterRole:
v.Kind = "ClusterRole"
v.APIVersion = "rbac.authorization.k8s.io/v1"
v.SetSelfLink(fmt.Sprintf("/api/rbac.authorization.k8s.io/v1/clusterroles/%s/%s",
object.GetNamespace(),
object.GetName()))
case *rbac.ClusterRoleBinding:
v.Kind = "ClusterRoleBinding"
v.APIVersion = "rbac.authorization.k8s.io/v1"
v.SetSelfLink(fmt.Sprintf("/api/rbac.authorization.k8s.io/v1/clusterrolebingings/%s/%s",
object.GetNamespace(),
object.GetName()))
case *rbac.Role:
v.Kind = "Role"
v.APIVersion = "rbac.authorization.k8s.io/v1"
v.SetSelfLink(fmt.Sprintf("/api/rbac.authorization.k8s.io/v1/roles/%s/%s",
object.GetNamespace(),
object.GetName()))
case *rbac.RoleBinding:
v.Kind = "RoleBinding"
v.APIVersion = "rbac.authorization.k8s.io/v1"
v.SetSelfLink(fmt.Sprintf("/api/rbac.authorization.k8s.io/v1/rolebingings/%s/%s",
object.GetNamespace(),
object.GetName()))
default: default:
require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String())) require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String()))
} }
@ -398,7 +585,9 @@ func NewMetaObject[T meta.Object](t *testing.T, namespace, name string, mods ...
reflect.ValueOf(&obj).Elem().Set(newObj) reflect.ValueOf(&obj).Elem().Set(newObj)
} }
obj.SetNamespace(namespace) if IsNamespaced(obj) {
obj.SetNamespace(namespace)
}
obj.SetName(name) obj.SetName(name)
obj.SetUID(uuid.NewUUID()) obj.SetUID(uuid.NewUUID())
@ -411,6 +600,15 @@ func NewMetaObject[T meta.Object](t *testing.T, namespace, name string, mods ...
return obj return obj
} }
func IsNamespaced(in meta.Object) bool {
switch in.(type) {
case *rbac.ClusterRole, *rbac.ClusterRoleBinding:
return false
default:
return true
}
}
func NewItem(t *testing.T, o operation.Operation, object meta.Object) operation.Item { func NewItem(t *testing.T, o operation.Operation, object meta.Object) operation.Item {
item := operation.Item{ item := operation.Item{
Operation: o, Operation: o,
@ -432,6 +630,10 @@ func NewItem(t *testing.T, o operation.Operation, object meta.Object) operation.
item.Group = "" item.Group = ""
item.Version = "v1" item.Version = "v1"
item.Kind = "Secret" item.Kind = "Secret"
case *core.ServiceAccount:
item.Group = ""
item.Version = "v1"
item.Kind = "ServiceAccount"
case *api.ArangoDeployment: case *api.ArangoDeployment:
item.Group = deployment.ArangoDeploymentGroupName item.Group = deployment.ArangoDeploymentGroupName
item.Version = api.ArangoDeploymentVersion item.Version = api.ArangoDeploymentVersion
@ -452,6 +654,22 @@ func NewItem(t *testing.T, o operation.Operation, object meta.Object) operation.
item.Group = ml.ArangoMLGroupName item.Group = ml.ArangoMLGroupName
item.Version = mlApi.ArangoMLVersion item.Version = mlApi.ArangoMLVersion
item.Kind = ml.ArangoMLStorageResourceKind item.Kind = ml.ArangoMLStorageResourceKind
case *rbac.ClusterRole:
item.Group = "rbac.authorization.k8s.io"
item.Version = "v1"
item.Kind = "ClusterRole"
case *rbac.ClusterRoleBinding:
item.Group = "rbac.authorization.k8s.io"
item.Version = "v1"
item.Kind = "ClusterRoleBinding"
case *rbac.Role:
item.Group = "rbac.authorization.k8s.io"
item.Version = "v1"
item.Kind = "Role"
case *rbac.RoleBinding:
item.Group = "rbac.authorization.k8s.io"
item.Version = "v1"
item.Kind = "RoleBinding"
default: default:
require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String())) require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String()))
} }

View file

@ -27,6 +27,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
batch "k8s.io/api/batch/v1" batch "k8s.io/api/batch/v1"
core "k8s.io/api/core/v1" core "k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1"
backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1"
@ -62,6 +63,11 @@ func Test_NewMetaObject(t *testing.T) {
NewMetaObjectRun[*batch.Job](t) NewMetaObjectRun[*batch.Job](t)
NewMetaObjectRun[*core.Pod](t) NewMetaObjectRun[*core.Pod](t)
NewMetaObjectRun[*core.Secret](t) NewMetaObjectRun[*core.Secret](t)
NewMetaObjectRun[*core.ServiceAccount](t)
NewMetaObjectRun[*rbac.Role](t)
NewMetaObjectRun[*rbac.RoleBinding](t)
NewMetaObjectRun[*rbac.ClusterRole](t)
NewMetaObjectRun[*rbac.ClusterRoleBinding](t)
NewMetaObjectRun[*api.ArangoDeployment](t) NewMetaObjectRun[*api.ArangoDeployment](t)
NewMetaObjectRun[*api.ArangoClusterSynchronization](t) NewMetaObjectRun[*api.ArangoClusterSynchronization](t)
NewMetaObjectRun[*backupApi.ArangoBackup](t) NewMetaObjectRun[*backupApi.ArangoBackup](t)