func (r *GameServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := log.FromContext(ctx)
	var gs mpsv1alpha1.GameServer
	if err := r.Get(ctx, req.NamespacedName, &gs); err != nil {
		if apierrors.IsNotFound(err) {
			log.Info("Unable to fetch GameServer - skipping")
			return ctrl.Result{}, nil
		}
		log.Error(err, "unable to fetch GameServer")
		return ctrl.Result{}, err
	}
	if gs.ObjectMeta.DeletionTimestamp.IsZero() {
		if !containsString(gs.GetFinalizers(), finalizerName) {
			controllerutil.AddFinalizer(&gs, finalizerName)
			if err := r.Update(ctx, &gs); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{}, nil
		}
	} else {
		if containsString(gs.GetFinalizers(), finalizerName) {
			r.unassignPorts(&gs)
			controllerutil.RemoveFinalizer(&gs, finalizerName)
			if err := r.Update(ctx, &gs); err != nil {
				return ctrl.Result{}, err
			}
		}
		return ctrl.Result{}, nil
	}
	var pod corev1.Pod
	podFoundInCache := true
	if err := r.Get(ctx, types.NamespacedName{Namespace: gs.Namespace, Name: gs.Name}, &pod); err != nil {
		if apierrors.IsNotFound(err) {
			podFoundInCache = false
		} else {
			return ctrl.Result{}, err
		}
	}
	_, podUnderCreation := podsUnderCreation.Get(gs.Name)
	if !podFoundInCache && podUnderCreation {
		return ctrl.Result{}, nil
	} else if podUnderCreation {
		podsUnderCreation.Del(gs.Name)
	}
	if !podFoundInCache {
		log.Info("Creating a new pod for GameServer", GameServerKind, gs.Name)
		newPod := NewPodForGameServer(&gs)
		if err := r.Create(ctx, newPod); err != nil {
			return ctrl.Result{}, err
		}
		podsUnderCreation.Set(gs.Name, struct{}{})
		r.Recorder.Eventf(&gs, corev1.EventTypeNormal, "Created", "Created new pod %s for GameServer %s", newPod.Name, gs.Name)
		return ctrl.Result{}, nil
	}
	for _, containerStatus := range pod.Status.ContainerStatuses {
		if containerStatus.Name == SidecarContainerName {
			continue
		}
		if !containerStatus.Ready && containerStatus.State.Terminated != nil {
			exitCode := containerStatus.State.Terminated.ExitCode
			r.Recorder.Eventf(&gs, corev1.EventTypeNormal, "GameServerProcessExited", "GameServer process exited with code %d", exitCode)
			if exitCode == 0 {
				gs.Status.State = mpsv1alpha1.GameServerStateGameCompleted
			} else {
				gs.Status.State = mpsv1alpha1.GameServerStateCrashed
			}
			if err := r.Status().Update(ctx, &gs); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{}, nil
		}
	}
	r.Recorder.Eventf(&gs, corev1.EventTypeNormal, "Update", "Gameserver %s state is %s", gs.Name, gs.Status.State)
	podAnnotations := pod.GetAnnotations()
	if podAnnotations == nil {
		podAnnotations = make(map[string]string)
	}
	if gs.Status.State == mpsv1alpha1.GameServerStateActive {
		podAnnotations[safeToEvictPodAttribute] = "false"
	} else {
		podAnnotations[safeToEvictPodAttribute] = "true"
	}
	pod.SetAnnotations(podAnnotations)
	r.Update(ctx, &pod)
	if gs.Status.PublicIP == "" {
		if pod.Spec.NodeName == "" {
			return ctrl.Result{}, nil  
		}
		publicIP, err := r.GetPublicIpForNodeProvider(ctx, r, pod.Spec.NodeName)
		if err != nil {
			return ctrl.Result{}, err
		}
		gs.Status.PublicIP = publicIP
		gs.Status.Ports = getContainerHostPortTuples(&pod)
		err = r.Status().Update(ctx, &gs)
		if err != nil {
			if apierrors.IsConflict(err) {  
				return ctrl.Result{Requeue: true}, nil
			} else {
				return ctrl.Result{}, err
			}
		}
	}
	return ctrl.Result{}, nil
}
we request secret RBAC access here so they can be potentially used by the API service (for GameServer allocations)
+kubebuilder:rbac:groups=mps.playfab.com,resources=gameservers,verbs=get;list;watch;create;update;patch;delete
+kubebuilder:rbac:groups=mps.playfab.com,resources=gameservers/status,verbs=get;update;patch
+kubebuilder:rbac:groups=mps.playfab.com,resources=gameservers/finalizers,verbs=update
+kubebuilder:rbac:groups="",resources=nodes,verbs=get;list;watch
+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;update;patch;delete
+kubebuilder:rbac:groups="",resources=events,verbs=create;patch
+kubebuilder:rbac:groups="",resources=secrets,verbs=get
Reconcile is part of the main kubernetes reconciliation loop which aims to
move the current state of the cluster closer to the desired state.
For more details, check Reconcile and its Result here:
- https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile
package controllers
import (
	"context"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/record"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"sigs.k8s.io/controller-runtime/pkg/log"
	mpsv1alpha1 "github.com/playfab/thundernetes/operator/api/v1alpha1"
	hm "github.com/cornelk/hashmap"
)
var (
	ownerKey = ".metadata.controller"
	apiGVStr = mpsv1alpha1.GroupVersion.String()
	podsUnderCreation = &hm.HashMap{}
)
const safeToEvictPodAttribute string = "cluster-autoscaler.kubernetes.io/safe-to-evict"
const finalizerName string = "gameservers.mps.playfab.com/finalizer"
type GameServerReconciler struct {
	client.Client
	Scheme                     *runtime.Scheme
	Recorder                   record.EventRecorder
	PortRegistry               *PortRegistry
	GetPublicIpForNodeProvider func(ctx context.Context, r client.Reader, nodeName string) (string, error)  
}