Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions chart/seeder/templates/rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ rules:
- admissionregistration.k8s.io
resources:
- validatingwebhookconfigurations
- mutatingwebhookconfigurations
verbs:
- get
- watch
Expand Down
3 changes: 3 additions & 0 deletions pkg/api/v1alpha1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ const (
DefaultEndpointPort = 9090
DefaultSeederDeploymentService = "harvester-seeder-endpoint"
DefaultHegelDeploymentEndpointLookup = "smee"
ClusterOwnerKey = "harvesterhci.io/clusterOwner"
ClusterOwnerDetailsKey = "harvesterhci.io/clusterOwnerDetails"
ExtraFieldKey = "username"
)
39 changes: 39 additions & 0 deletions pkg/controllers/cluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ func (r *ClusterReconciler) patchNodesAndPools(ctx context.Context, cObj *seeder
continue
}

i, err = r.patchInventoryOwnership(ctx, cObj, i)
if err != nil {
return fmt.Errorf("error updating inventory ownership: %v", err)
}
var found bool
var nodeAddress string
for address, nodeDetails := range pool.Status.AddressAllocation {
Expand Down Expand Up @@ -775,3 +779,38 @@ func (r *ClusterReconciler) lockedAddressPoolUpdate(ctx context.Context, pool *s
defer r.mutex.Unlock()
return r.Status().Update(ctx, pool)
}

// patchInventoryOwnership looks up ownership info from cluster object
// and labels the inventory with same info
func (r *ClusterReconciler) patchInventoryOwnership(ctx context.Context, c *seederv1alpha1.Cluster, i *seederv1alpha1.Inventory) (*seederv1alpha1.Inventory, error) {
if c.Annotations == nil {
return i, nil
}

clusterOwner, ok := c.Labels[seederv1alpha1.ClusterOwnerKey]
// no ownership details found on cluster, so no further action required
if !ok {
return i, nil
}

if i.Labels == nil {
i.Labels = make(map[string]string)
}
// apply user info
i.Labels[seederv1alpha1.ClusterOwnerKey] = clusterOwner

// apply user details
clusterOwnerDetails, ok := c.Labels[seederv1alpha1.ClusterOwnerDetailsKey]
if ok {
i.Labels[seederv1alpha1.ClusterOwnerDetailsKey] = clusterOwnerDetails
}

err := r.Update(ctx, i)
if err != nil {
return i, fmt.Errorf("error updating inventory with ownership details: %v", err)
}

iObj := &seederv1alpha1.Inventory{}
err = r.Get(ctx, types.NamespacedName{Name: i.Name, Namespace: i.Namespace}, iObj)
return iObj, err
}
7 changes: 7 additions & 0 deletions pkg/controllers/inventory_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,13 @@ func (r *InventoryReconciler) reconcileBMCJob(ctx context.Context, iObj *seederv
func (r *InventoryReconciler) inventoryFreed(ctx context.Context, iObj *seederv1alpha1.Inventory) error {
i := iObj.DeepCopy()
if util.ConditionExists(i, seederv1alpha1.InventoryFreed) {
if i.Labels != nil {
if _, ok := i.Labels[seederv1alpha1.ClusterOwnerKey]; ok {
delete(i.Labels, seederv1alpha1.ClusterOwnerKey)
delete(i.Labels, seederv1alpha1.ClusterOwnerDetailsKey)
return r.Update(ctx, i)
}
}
j := util.GenerateJob(i.Name, i.Namespace, "shutdown")
if err := r.jobWrapper(ctx, i, j); err != nil {
return err
Expand Down
18 changes: 9 additions & 9 deletions pkg/data/data.go

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

69 changes: 69 additions & 0 deletions pkg/webhook/cluster_mutator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package webhook

import (
"context"
"strings"

werror "github.com/harvester/webhook/pkg/error"
"github.com/harvester/webhook/pkg/server/admission"
admissionregv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"

seederv1alpha1 "github.com/harvester/seeder/pkg/api/v1alpha1"
)

type ClusterMutator struct {
client client.Client
ctx context.Context
admission.DefaultMutator
}

func NewClusterMutator(ctx context.Context, mgr manager.Manager) *ClusterMutator {
return &ClusterMutator{
client: mgr.GetClient(),
ctx: ctx,
}
}

func (c *ClusterMutator) Resource() admission.Resource {
return admission.Resource{
Names: []string{"clusters"},
Scope: admissionregv1.NamespacedScope,
APIGroup: seederv1alpha1.GroupVersion.Group,
APIVersion: seederv1alpha1.GroupVersion.Version,
ObjectType: &seederv1alpha1.Cluster{},
OperationTypes: []admissionregv1.OperationType{
admissionregv1.Create,
},
}
}

func (c *ClusterMutator) Create(req *admission.Request, newObj runtime.Object) (admission.Patch, error) {
clusterObj, ok := newObj.(*seederv1alpha1.Cluster)
if !ok {
return nil, werror.NewBadRequest("unable to assert object to Cluster Object")
}
var patchOps admission.Patch
userName := req.UserInfo.Username

// if there is an existing owner reference, then replace it with details in request
labels := clusterObj.GetLabels()
if labels == nil {
labels = make(map[string]string)
}

labels[seederv1alpha1.ClusterOwnerKey] = userName
val, ok := req.UserInfo.Extra[seederv1alpha1.ExtraFieldKey]
if ok {
labels[seederv1alpha1.ClusterOwnerDetailsKey] = strings.Join(val, " ")
}

patchOps = append(patchOps, admission.PatchOp{
Op: admission.PatchOpAdd,
Path: "/metadata/labels",
Value: labels,
})
return patchOps, nil
}
87 changes: 87 additions & 0 deletions pkg/webhook/cluster_mutator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package webhook

import (
"context"
"encoding/json"
"testing"

seederv1alpha1 "github.com/harvester/seeder/pkg/api/v1alpha1"
"github.com/harvester/webhook/pkg/server/admission"
"github.com/rancher/wrangler/pkg/webhook"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/admission/v1"
authenticationv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

var (
clusterObj = &seederv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster",
},
}

userName = "dev"

request = &admission.Request{
Request: &webhook.Request{
AdmissionRequest: v1.AdmissionRequest{
UserInfo: authenticationv1.UserInfo{
Username: userName,
},
},
},
}
)

func Test_ClusterMutation(t *testing.T) {
type testCases struct {
name string
generateClusterObject func(*seederv1alpha1.Cluster) *seederv1alpha1.Cluster
}

var cases = []testCases{
{
name: "no cluster owner defined",
generateClusterObject: func(cluster *seederv1alpha1.Cluster) *seederv1alpha1.Cluster { return cluster },
},
{
name: "cluster object has an owner defined",
generateClusterObject: func(cluster *seederv1alpha1.Cluster) *seederv1alpha1.Cluster {
clusterCopy := cluster.DeepCopy()
clusterCopy.Labels = map[string]string{
seederv1alpha1.ClusterOwnerKey: "demo",
}
return clusterCopy
},
},
}

for _, testCase := range cases {
assert := require.New(t)
scheme := runtime.NewScheme()
err := seederv1alpha1.AddToScheme(scheme)
assert.NoError(err, testCase.name)
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(testCase.generateClusterObject(clusterObj)).Build()
c := &ClusterMutator{
client: fakeClient,
ctx: context.TODO(),
}
ops, err := c.Create(request, clusterObj)
assert.NoError(err, testCase.name)
assert.Len(ops, 1, testCase.name)
// test out by patching the object
opsByte, err := json.Marshal(ops)
assert.NoError(err, testCase.name)
err = c.client.Patch(context.TODO(), clusterObj, client.RawPatch(types.JSONPatchType, opsByte))
assert.NoError(err, testCase.name)
updatedObj := &seederv1alpha1.Cluster{}
err = c.client.Get(context.TODO(), types.NamespacedName{Name: clusterObj.Name, Namespace: clusterObj.Namespace}, updatedObj)
assert.NoError(err, testCase.name)
assert.Equal(updatedObj.GetLabels()[seederv1alpha1.ClusterOwnerKey], userName, testCase.name)
}
}
3 changes: 3 additions & 0 deletions pkg/webhook/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ func SetupWebhookServer(ctx context.Context, mgr manager.Manager, namespace stri
return err
}

if err := webhookServer.RegisterMutators(NewClusterMutator(ctx, mgr)); err != nil {
return err
}
// since webhook and manager start run as two go routines, need to wait for caches to sync
// before starting the server
for {
Expand Down