diff --git a/api/v1alpha1/image_types.go b/api/v1alpha1/image_types.go index 91dae82..7f7e93c 100644 --- a/api/v1alpha1/image_types.go +++ b/api/v1alpha1/image_types.go @@ -23,11 +23,23 @@ import ( // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. +type InitScript struct { + Image string `json:"image"` + Command []string `json:"command"` + Args []string `json:"args"` +} + // ImageSpec defines the desired state of Image type ImageSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - Location string `json:"location"` + Name string `json:"name"` + Location string `json:"location"` + WorkingDir string `json:"working_dir"` + Command []string `json:"command,omitempty"` + Ports []Port `json:"ports"` + Args []string `json:"args,omitempty"` + InitScript *InitScript `json:"init_script,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index d899568..f04c26f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -59,7 +59,7 @@ func (in *Image) DeepCopyInto(out *Image) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Image. @@ -115,6 +115,26 @@ func (in *ImageList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImageSpec) DeepCopyInto(out *ImageSpec) { *out = *in + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]Port, len(*in)) + copy(*out, *in) + } + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.InitScript != nil { + in, out := &in.InitScript, &out.InitScript + *out = new(InitScript) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageSpec. @@ -127,6 +147,31 @@ func (in *ImageSpec) DeepCopy() *ImageSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InitScript) DeepCopyInto(out *InitScript) { + *out = *in + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InitScript. +func (in *InitScript) DeepCopy() *InitScript { + if in == nil { + return nil + } + out := new(InitScript) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Port) DeepCopyInto(out *Port) { *out = *in diff --git a/config/crd/bases/server-manager.acooldomain.co_images.yaml b/config/crd/bases/server-manager.acooldomain.co_images.yaml index dbd9f11..d44f138 100644 --- a/config/crd/bases/server-manager.acooldomain.co_images.yaml +++ b/config/crd/bases/server-manager.acooldomain.co_images.yaml @@ -39,13 +39,57 @@ spec: spec: description: ImageSpec defines the desired state of Image properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + init_script: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + image: + type: string + required: + - args + - command + - image + type: object location: + type: string + name: description: |- INSERT ADDITIONAL SPEC FIELDS - desired state of cluster Important: Run "make" to regenerate code after modifying this file type: string + ports: + items: + properties: + port: + format: int32 + type: integer + protocol: + description: Protocol defines network protocols supported for + things like container ports. + type: string + type: object + type: array + working_dir: + type: string required: - location + - name + - ports + - working_dir type: object type: object served: true diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 3ed2950..eeb202d 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -19,6 +19,7 @@ rules: - pods verbs: - create + - delete - get - list - watch diff --git a/config/samples/server-manager_v1alpha1_image.yaml b/config/samples/server-manager_v1alpha1_image.yaml index b4d0718..9459032 100644 --- a/config/samples/server-manager_v1alpha1_image.yaml +++ b/config/samples/server-manager_v1alpha1_image.yaml @@ -4,6 +4,20 @@ metadata: labels: app.kubernetes.io/name: kubernetes-operator app.kubernetes.io/managed-by: kustomize - name: image-sample + name: minecraft-paper-1-21-4 spec: - # TODO(user): Add fields here + location: git.acooldomain.co/server-manager/minecraft:paper-1.21.4 + name: minecraft:paper-1.21.4 + working_dir: /opt/server + ports: + - port: 25565 + protocol: TCP + + init_script: + image: alpine:latest + command: + - /bin/sh + args: + - /bin/sh + - "-c" + - "echo eula=true >> /data/eula.txt" diff --git a/config/samples/server-manager_v1alpha1_servermanager.yaml b/config/samples/server-manager_v1alpha1_servermanager.yaml index 7770335..e111ea1 100644 --- a/config/samples/server-manager_v1alpha1_servermanager.yaml +++ b/config/samples/server-manager_v1alpha1_servermanager.yaml @@ -11,8 +11,7 @@ spec: "on": true server: "on": true - image: git.acooldomain.co/server-manager/minecraft:paper-1.21.4 - working_dir: /opt/server + image: minecraft-paper-1-21-4 ports: - protocol: TCP port: 25565 diff --git a/internal/controller/servermanager_controller.go b/internal/controller/servermanager_controller.go index ccd44f8..f7f1ea1 100644 --- a/internal/controller/servermanager_controller.go +++ b/internal/controller/servermanager_controller.go @@ -33,6 +33,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" servermanagerv1alpha1 "git.acooldomain.co/server-manager/kubernetes-operator/api/v1alpha1" ) @@ -95,7 +96,7 @@ type ServerManagerReconciler struct { // +kubebuilder:rbac:groups=,resources=persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=server-manager.acooldomain.co,resources=servermanagers/finalizers,verbs=update // +kubebuilder:rbac:groups=,resources=services,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=,resources=pods,verbs=get;list;watch;create +// +kubebuilder:rbac:groups=,resources=pods,verbs=get;list;watch;create;delete // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -151,7 +152,13 @@ func (r *ServerManagerReconciler) Reconcile(ctx context.Context, req ctrl.Reques } logging.Info("verified browserPvc") - serverPod := r.ServerPod(s, pvc) + image, err := r.GetImage(ctx, s) + if err != nil { + logging.Error(err, "Failed to get image") + return reconcile.Result{}, err + } + + serverPod := r.ServerPod(s, pvc, image) found := &corev1.Pod{} err = r.Get(ctx, client.ObjectKey{Namespace: pvc.Namespace, Name: pvc.Name}, found) if err == nil && !s.Spec.Server.On { @@ -602,8 +609,13 @@ func (r *ServerManagerReconciler) ServerPvc(s *servermanagerv1alpha1.ServerManag return pvc } -func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManager, pvc *corev1.PersistentVolumeClaim) *corev1.Pod { - ports := make([]corev1.ContainerPort, len(s.Spec.Server.Ports)) +func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManager, pvc *corev1.PersistentVolumeClaim, image *servermanagerv1alpha1.Image) *corev1.Pod { + serverPorts := image.Spec.Ports + if len(s.Spec.Server.Ports) > 0 { + serverPorts = s.Spec.Server.Ports + } + + ports := make([]corev1.ContainerPort, len(serverPorts)) for i, port := range s.Spec.Server.Ports { ports[i] = corev1.ContainerPort{ @@ -612,6 +624,36 @@ func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManag } } + command := image.Spec.Command + if len(s.Spec.Server.Command) > 0 { + command = s.Spec.Server.Command + } + + args := image.Spec.Args + if len(s.Spec.Server.Args) > 0 { + args = s.Spec.Server.Args + } + + var initContainers []corev1.Container = nil + + if image.Spec.InitScript != nil { + initContainers = []corev1.Container{ + { + Name: "init", + Image: image.Spec.InitScript.Image, + ImagePullPolicy: corev1.PullIfNotPresent, + Command: image.Spec.Command, + Args: image.Spec.InitScript.Args, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "volume", + MountPath: "/data", + }, + }, + }, + } + } + pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: s.Name, @@ -629,18 +671,19 @@ func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManag }, }, }, + InitContainers: initContainers, Containers: []corev1.Container{ { Name: "server", - Image: s.Spec.Server.Image, + Image: image.Spec.Location, ImagePullPolicy: corev1.PullAlways, - Command: s.Spec.Server.Command, - Args: s.Spec.Server.Args, - WorkingDir: s.Spec.Server.WorkingDir, + Command: command, + Args: args, + WorkingDir: image.Spec.WorkingDir, Ports: ports, VolumeMounts: []corev1.VolumeMount{{ Name: "volume", - MountPath: s.Spec.Server.WorkingDir, + MountPath: image.Spec.WorkingDir, }}, Stdin: true, TTY: true, @@ -648,10 +691,21 @@ func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManag }, }, } + controllerutil.SetControllerReference(s, pod, r.Scheme) return pod } +func (r *ServerManagerReconciler) GetImage(ctx context.Context, s *servermanagerv1alpha1.ServerManager) (*servermanagerv1alpha1.Image, error) { + image := &servermanagerv1alpha1.Image{} + err := r.Get(ctx, client.ObjectKey{Name: s.Spec.Server.Image, Namespace: s.Namespace}, image) + if err != nil { + return nil, err + } + + return image, nil +} + // SetupWithManager sets up the controller with the Manager. func (r *ServerManagerReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr).