Skip to content

Commit

Permalink
feat: create proxy rbac
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremylvln committed Mar 5, 2022
1 parent 641ac78 commit 20d2421
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 70 deletions.
26 changes: 17 additions & 9 deletions internal/resource/cluster/builder.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package resource

import (
"fmt"

"k8s.io/apimachinery/pkg/runtime"
shulkermciov1alpha1 "shulkermc.io/m/v2/api/v1alpha1"
common "shulkermc.io/m/v2/internal/resource"
Expand All @@ -12,17 +14,23 @@ type MinecraftClusterResourceBuilder struct {
}

func (b *MinecraftClusterResourceBuilder) ResourceBuilders() ([]common.ResourceBuilder, []common.ResourceBuilder) {
builders := []common.ResourceBuilder{}
builders := []common.ResourceBuilder{
b.MinecraftClusterRole(),
}
dirtyBuilders := []common.ResourceBuilder{}

return builders, dirtyBuilders
}

// func (b *MinecraftClusterResourceBuilder) getLabels() map[string]string {
// return map[string]string{
// "app.kubernetes.io/name": b.Instance.Name,
// "app.kubernetes.io/component": "minecraft-cluster",
// "app.kubernetes.io/part-of": "shulker",
// "app.kubernetes.io/created-by": "shulker",
// }
// }
func (b *MinecraftClusterResourceBuilder) getRoleName() string {
return fmt.Sprintf("%s-cluster-status-watch", b.Instance.Name)
}

func (b *MinecraftClusterResourceBuilder) getLabels() map[string]string {
return map[string]string{
"app.kubernetes.io/name": b.Instance.Name,
"app.kubernetes.io/component": "minecraft-cluster",
"app.kubernetes.io/part-of": "shulker",
"app.kubernetes.io/created-by": "shulker",
}
}
51 changes: 51 additions & 0 deletions internal/resource/cluster/role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package resource

import (
"fmt"

rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type MinecraftClusterRoleBuilder struct {
*MinecraftClusterResourceBuilder
}

func (b *MinecraftClusterResourceBuilder) MinecraftClusterRole() *MinecraftClusterRoleBuilder {
return &MinecraftClusterRoleBuilder{b}
}

func (b *MinecraftClusterRoleBuilder) Build() (client.Object, error) {
return &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: b.getRoleName(),
Namespace: b.Instance.Namespace,
Labels: b.getLabels(),
},
}, nil
}

func (b *MinecraftClusterRoleBuilder) Update(object client.Object) error {
role := object.(*rbacv1.Role)

role.Rules = []rbacv1.PolicyRule{
{
APIGroups: []string{"shulkermc.io"},
Resources: []string{"minecraftclusters/status"},
Verbs: []string{"get", "watch"},
ResourceNames: []string{b.Instance.Name},
},
}

if err := controllerutil.SetControllerReference(b.Instance, role, b.Scheme); err != nil {
return fmt.Errorf("failed setting controller reference for Role: %v", err)
}

return nil
}

func (b *MinecraftClusterRoleBuilder) CanBeUpdated() bool {
return true
}
10 changes: 10 additions & 0 deletions internal/resource/proxy/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ func (b *ProxyDeploymentResourceBuilder) ResourceBuilders() ([]common.ResourceBu
b.ProxyDeploymentDeployment(),
b.ProxyDeploymentConfigMap(),
b.ProxyDeploymentService(),
b.ProxyDeploymentServiceAccount(),
b.ProxyDeploymentRoleBinding(),
}
dirtyBuilders := []common.ResourceBuilder{}

Expand All @@ -41,6 +43,14 @@ func (b *ProxyDeploymentResourceBuilder) getServiceName() string {
return fmt.Sprintf("%s-proxy-%s", b.getResourcePrefix(), b.Instance.Name)
}

func (b *ProxyDeploymentResourceBuilder) getServiceAccountName() string {
return fmt.Sprintf("%s-proxy-%s", b.getResourcePrefix(), b.Instance.Name)
}

func (b *ProxyDeploymentResourceBuilder) getRoleBindingName() string {
return fmt.Sprintf("%s-proxy-%s", b.getResourcePrefix(), b.Instance.Name)
}

func (b *ProxyDeploymentResourceBuilder) getLabels() map[string]string {
labels := map[string]string{
"app.kubernetes.io/name": b.Instance.Name,
Expand Down
1 change: 1 addition & 0 deletions internal/resource/proxy/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ func (b *ProxyDeploymentDeploymentBuilder) Update(object client.Object) error {
},
},
}},
ServiceAccountName: b.getServiceAccountName(),
TerminationGracePeriodSeconds: b.Instance.Spec.PodOverrides.TerminationGracePeriodSeconds,
Affinity: b.Instance.Spec.Affinity,
Volumes: []corev1.Volume{
Expand Down
53 changes: 53 additions & 0 deletions internal/resource/proxy/rolebinding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package resource

import (
"fmt"

rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type ProxyDeploymentRoleBindingBuilder struct {
*ProxyDeploymentResourceBuilder
}

func (b *ProxyDeploymentResourceBuilder) ProxyDeploymentRoleBinding() *ProxyDeploymentRoleBindingBuilder {
return &ProxyDeploymentRoleBindingBuilder{b}
}

func (b *ProxyDeploymentRoleBindingBuilder) Build() (client.Object, error) {
return &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: b.getRoleBindingName(),
Namespace: b.Instance.Namespace,
Labels: b.getLabels(),
},
}, nil
}

func (b *ProxyDeploymentRoleBindingBuilder) Update(object client.Object) error {
roleBinding := object.(*rbacv1.RoleBinding)

roleBinding.RoleRef = rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: fmt.Sprintf("%s-cluster-status-watch", b.Cluster.Name),
}

roleBinding.Subjects = []rbacv1.Subject{{
Kind: "ServiceAccount",
Name: b.getServiceAccountName(),
}}

if err := controllerutil.SetControllerReference(b.Instance, roleBinding, b.Scheme); err != nil {
return fmt.Errorf("failed setting controller reference for RoleBinding: %v", err)
}

return nil
}

func (b *ProxyDeploymentRoleBindingBuilder) CanBeUpdated() bool {
return true
}
42 changes: 42 additions & 0 deletions internal/resource/proxy/serviceaccount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package resource

import (
"fmt"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type ProxyDeploymentServiceAccountBuilder struct {
*ProxyDeploymentResourceBuilder
}

func (b *ProxyDeploymentResourceBuilder) ProxyDeploymentServiceAccount() *ProxyDeploymentServiceAccountBuilder {
return &ProxyDeploymentServiceAccountBuilder{b}
}

func (b *ProxyDeploymentServiceAccountBuilder) Build() (client.Object, error) {
return &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: b.getServiceAccountName(),
Namespace: b.Instance.Namespace,
Labels: b.getLabels(),
},
}, nil
}

func (b *ProxyDeploymentServiceAccountBuilder) Update(object client.Object) error {
serviceAccount := object.(*corev1.ServiceAccount)

if err := controllerutil.SetControllerReference(b.Instance, serviceAccount, b.Scheme); err != nil {
return fmt.Errorf("failed setting controller reference for ServiceAccount: %v", err)
}

return nil
}

func (b *ProxyDeploymentServiceAccountBuilder) CanBeUpdated() bool {
return true
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package io.shulkermc.directory;

import io.kubernetes.client.informer.ResourceEventHandler;
import io.kubernetes.client.informer.SharedIndexInformer;
import io.kubernetes.client.informer.SharedInformerFactory;
import com.google.common.reflect.TypeToken;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.apis.CustomObjectsApi;
import io.kubernetes.client.util.CallGeneratorParams;
import io.kubernetes.client.util.ClientBuilder;
import io.kubernetes.client.util.Watch;
import io.shulkermc.models.V1alpha1MinecraftCluster;
import io.shulkermc.models.V1alpha1MinecraftClusterList;
import io.shulkermc.models.V1alpha1MinecraftClusterStatus;
import io.shulkermc.models.V1alpha1MinecraftClusterStatusServerPool;
import net.md_5.bungee.api.ProxyServer;
Expand All @@ -21,13 +18,14 @@
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

public class ShulkerProxyDirectory extends Plugin {
private final ProxyServer proxyServer;

private String shulkerClusterName;
private CustomObjectsApi kubernetesObjectApi;
private SharedInformerFactory kubernetesInformerFactory;
private Thread reconcilerThread;
private final AtomicBoolean reconcilerContinue = new AtomicBoolean(true);

public ShulkerProxyDirectory() {
this.proxyServer = ProxyServer.getInstance();
Expand Down Expand Up @@ -55,30 +53,53 @@ public void onEnable() {
return;
}

this.kubernetesObjectApi = new CustomObjectsApi(kubernetesClient);
this.kubernetesInformerFactory = new SharedInformerFactory();

this.createInformer();
CustomObjectsApi customObjectsApi = new CustomObjectsApi(kubernetesClient);

this.reconcilerThread = new Thread(() -> {
while (this.reconcilerContinue.get()) {
try {
this.getLogger().info("Reconciling cluster status");
Watch<V1alpha1MinecraftClusterStatus> watch = Watch.createWatch(
kubernetesClient,
customObjectsApi.getClusterCustomObjectStatusCall(
"shulkermc.io",
"v1alpha1",
"minecraftclusters",
this.shulkerClusterName,
null
),
new TypeToken<Watch.Response<V1alpha1MinecraftClusterStatus>>(){}.getType());

for (var event : watch) {
if (!event.type.equals("MODIFIED")) continue;
V1alpha1MinecraftClusterStatus status = event.object;
this.updateServerDirectory(status.getServerPool());
}
} catch (ApiException ex) {
this.getLogger().severe("Failed to watch cluster status");
ex.printStackTrace();
}
}
}, "ShulkerClusterReconciler");
this.reconcilerThread.start();

try {
this.syncServerDirectory();
V1alpha1MinecraftClusterStatus status = (V1alpha1MinecraftClusterStatus) customObjectsApi.getClusterCustomObjectStatus(
"shulkermc.io", "v1alpha1", "minecraftclusters", this.shulkerClusterName);
this.updateServerDirectory(status.getServerPool());
} catch (ApiException ex) {
this.getLogger().severe("Failed to synchronize server directory");
ex.printStackTrace();
}

this.kubernetesInformerFactory.startAllRegisteredInformers();
}

@Override
public void onDisable() {
this.kubernetesInformerFactory.stopAllRegisteredInformers();
}

private void syncServerDirectory() throws ApiException {
V1alpha1MinecraftClusterStatus status = (V1alpha1MinecraftClusterStatus) this.kubernetesObjectApi.getClusterCustomObjectStatus(
"shulkermc.io", "v1alpha1", "minecraftclusters", this.shulkerClusterName);
this.updateServerDirectory(status.getServerPool());
try {
this.reconcilerContinue.set(false);
this.getLogger().info("Waiting for reconciler thread to finish");
this.reconcilerThread.wait();
} catch (InterruptedException ignored) {}
}

private void updateServerDirectory(List<V1alpha1MinecraftClusterStatusServerPool> serverPool) {
Expand All @@ -101,44 +122,4 @@ private void updateServerDirectory(List<V1alpha1MinecraftClusterStatusServerPool
.peek((serverName) -> this.getLogger().info(String.format("Removing server %s from directory", serverName)))
.forEach(proxyServers::remove);
}

private void createInformer() {
SharedIndexInformer<V1alpha1MinecraftCluster> minecraftClusterInformer =
this.kubernetesInformerFactory.sharedIndexInformerFor(
(CallGeneratorParams params) -> this.kubernetesObjectApi.listClusterCustomObjectCall(
"shulkermc.io",
"v1alpha1",
"minecraftclusters",
null,
null,
null,
null,
null,
params.resourceVersion,
params.timeoutSeconds,
params.watch,
null),
V1alpha1MinecraftCluster.class,
V1alpha1MinecraftClusterList.class);

minecraftClusterInformer.addEventHandler(new ResourceEventHandler<>() {
@Override
public void onAdd(V1alpha1MinecraftCluster obj) {}

@Override
public void onUpdate(V1alpha1MinecraftCluster oldObj, V1alpha1MinecraftCluster newObj) {
if (oldObj.getMetadata() == null
|| oldObj.getMetadata().getName() == null
|| !oldObj.getMetadata().getName().equals(ShulkerProxyDirectory.this.shulkerClusterName)) return;

V1alpha1MinecraftClusterStatus clusterStatus = newObj.getStatus();
if (clusterStatus == null) return;

ShulkerProxyDirectory.this.updateServerDirectory(clusterStatus.getServerPool());
}

@Override
public void onDelete(V1alpha1MinecraftCluster obj, boolean deletedFinalStateUnknown) {}
});
}
}

0 comments on commit 20d2421

Please sign in to comment.