backend/instancemanager/kubernetes/instance_manager.go
ACoolName ae12746ff2
Some checks failed
Build and Push Docker Image / Build image (push) Has been cancelled
added file browser to auth verify, setting up from clients
2025-04-09 17:34:27 +03:00

404 lines
11 KiB
Go

package kubernetes
import (
"context"
"io"
"log"
"os"
"path/filepath"
"strings"
"time"
"git.acooldomain.co/server-manager/backend/instancemanager"
"git.acooldomain.co/server-manager/backend/models"
servermanagerv1 "git.acooldomain.co/server-manager/kubernetes-operator/api/v1alpha1"
"github.com/buildkite/shellwords"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
names "k8s.io/apiserver/pkg/storage/names"
kubernetesclient "k8s.io/client-go/kubernetes"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/remotecommand"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type InstanceManager struct {
instancemanager.InstanceManager
Config models.KubernetesInstanceManagerConfig
client client.Client
restCfg *rest.Config
coreV1Cli *kubernetesclient.Clientset
}
func convertServerManagerImage(image *servermanagerv1.Image) *instancemanager.Image {
ports := make([]instancemanager.Port, len(image.Spec.Ports))
for i, port := range image.Spec.Ports {
ports[i] = instancemanager.Port{
Number: uint16(port.Port),
Protocol: models.PortProtocol(port.Protocol),
}
}
return &instancemanager.Image{
Registry: image.Spec.Name,
Tag: image.Spec.Tag,
Id: image.Name,
Ports: ports,
WorkingDir: image.Spec.WorkingDir,
Command: strings.Join(image.Spec.Args, " "),
}
}
// General
// Read Only
func (i *InstanceManager) GetImage(ctx context.Context, imageId string) (*instancemanager.Image, error) {
if imageId == "" {
return &instancemanager.Image{}, nil
}
image := &servermanagerv1.Image{}
err := i.client.Get(ctx, client.ObjectKey{Name: imageId, Namespace: i.Config.Namespace}, image)
if err != nil {
return nil, err
}
return convertServerManagerImage(image), nil
}
func (i *InstanceManager) ListImages(ctx context.Context) ([]instancemanager.Image, error) {
images := &servermanagerv1.ImageList{}
err := i.client.List(ctx, images, &client.ListOptions{Namespace: i.Config.Namespace})
if err != nil {
return nil, err
}
imImages := make([]instancemanager.Image, len(images.Items))
for i, image := range images.Items {
imImages[i] = *convertServerManagerImage(&image)
}
return imImages, nil
}
func (i *InstanceManager) GetServer(ctx context.Context, serverId string) (*instancemanager.Server, error) {
serverManager := &servermanagerv1.ServerManager{}
err := i.client.Get(ctx, client.ObjectKey{Namespace: i.Config.Namespace, Name: serverId}, serverManager)
if err != nil {
return nil, err
}
image, err := i.GetImage(ctx, serverManager.Spec.Server.Image)
if err != nil {
return nil, err
}
return &instancemanager.Server{
Id: serverManager.Name,
Running: serverManager.Spec.Server.On,
RunningCommand: strings.Join(serverManager.Status.Server.Args, " "),
RunningImage: &models.Image{
Registry: image.Registry,
Tag: image.Tag,
Id: image.Id,
},
}, nil
}
func (i *InstanceManager) ListServers(ctx context.Context) ([]instancemanager.Server, error) {
serverManagers := &servermanagerv1.ServerManagerList{}
err := i.client.List(ctx, serverManagers, &client.ListOptions{Namespace: i.Config.Namespace})
if err != nil {
return nil, err
}
servers := make([]instancemanager.Server, len(serverManagers.Items))
for index, serverManager := range serverManagers.Items {
image, err := i.GetImage(ctx, serverManager.Spec.Server.Image)
if err != nil {
return nil, err
}
ports := make([]models.Port, len(serverManager.Status.Server.HostPorts))
for i, port := range serverManager.Status.Server.HostPorts {
ports[i] = models.Port{
Protocol: models.PortProtocol(port.Protocol),
PublicPort: uint16(port.HostPort),
ContainerPort: uint16(port.TargetPort),
}
}
servers[index] = instancemanager.Server{
Id: serverManager.Name,
Running: serverManager.Spec.Server.On,
RunningCommand: strings.Join(serverManager.Status.Server.Args, " "),
Ports: ports,
Domain: serverManager.Status.Server.Domain,
RunningImage: &models.Image{
Registry: image.Registry,
Tag: image.Tag,
Id: image.Id,
},
}
}
return servers, nil
}
// State Changing
func (i *InstanceManager) StartServer(ctx context.Context, serverId string, imageId string, command string, ports []models.Port) error {
image := &servermanagerv1.Image{}
err := i.client.Get(ctx, client.ObjectKey{Name: imageId, Namespace: i.Config.Namespace}, image)
if err != nil {
return err
}
serverManager := &servermanagerv1.ServerManager{}
err = i.client.Get(ctx, client.ObjectKey{Name: serverId, Namespace: i.Config.Namespace}, serverManager)
if err != nil {
return err
}
serverManager.Spec.Server.Image = image.Name
serverManager.Spec.Server.On = true
serverManager.Spec.Server.Args, err = shellwords.Split(command)
if err != nil {
return err
}
err = i.client.Update(ctx, serverManager)
if err != nil {
return err
}
return nil
}
func (i *InstanceManager) StopServer(ctx context.Context, serverId string) error {
serverManager := &servermanagerv1.ServerManager{}
err := i.client.Get(ctx, client.ObjectKey{Name: serverId, Namespace: i.Config.Namespace}, serverManager)
if err != nil {
return err
}
serverManager.Spec.Server.On = false
err = i.client.Update(ctx, serverManager)
if err != nil {
return err
}
return nil
}
func (i *InstanceManager) CreateServer(ctx context.Context) (*instancemanager.Server, error) {
name := names.SimpleNameGenerator.GenerateName("")
err := i.client.Create(ctx, &servermanagerv1.ServerManager{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: i.Config.Namespace,
},
Spec: servermanagerv1.ServerManagerSpec{
Storage: "10Gi",
Server: servermanagerv1.ServerSpec{
Image: "",
On: false,
},
},
})
if err != nil {
return nil, err
}
serverManager := &instancemanager.Server{
Id: name,
Running: false,
RunningImage: nil,
RunningCommand: "",
}
return serverManager, nil
}
func (i *InstanceManager) DeleteServer(ctx context.Context, serverId string) error {
serverManager := &servermanagerv1.ServerManager{}
err := i.client.Get(ctx, client.ObjectKey{Name: serverId, Namespace: i.Config.Namespace}, serverManager)
if err != nil {
if errors.IsNotFound(err) {
return nil
} else {
return err
}
}
err = i.client.Create(ctx, serverManager)
if err != nil {
return err
}
return nil
}
// Terminal
// Status Changing
func (i *InstanceManager) InteractiveTerminal(ctx context.Context, serverId string) (*instancemanager.TerminalConnection, error) {
stdinReader, stdinWriter := io.Pipe()
stdoutReader, stdoutWriter := io.Pipe()
resizeChan := make(chan remotecommand.TerminalSize, 1)
queue := &sizeQueue{resizeChan: resizeChan}
req := i.coreV1Cli.CoreV1().RESTClient().
Post().
Resource("pods").
Name(serverId).
Namespace(i.Config.Namespace).
SubResource("attach").
VersionedParams(&corev1.PodAttachOptions{
Stdin: true,
Stdout: true,
Stderr: true,
TTY: true,
}, clientgoscheme.ParameterCodec)
executor, err := remotecommand.NewSPDYExecutor(i.restCfg, "POST", req.URL())
go func() {
defer stdoutWriter.Close()
defer stdinReader.Close()
err := executor.StreamWithContext(ctx, remotecommand.StreamOptions{Stdin: stdinReader, Stdout: stdoutWriter, Stderr: stdoutWriter, Tty: true, TerminalSizeQueue: queue})
if err != nil {
log.Printf("Failed to attach %e", err)
}
}()
if err != nil {
return nil, err
}
k := newKubeCon(stdinWriter, stdoutReader)
return &instancemanager.TerminalConnection{
Conn: k,
ResizerFunc: func(width uint, height uint) error {
resizeChan <- remotecommand.TerminalSize{Width: uint16(width), Height: uint16(height)}
return nil
},
}, nil
}
// File Browser
// Read Only
func (i *InstanceManager) GetFileBrowser(ctx context.Context, serverId string) (*models.FileBrowser, error) {
serverManager := &servermanagerv1.ServerManager{}
err := i.client.Get(ctx, client.ObjectKey{Namespace: i.Config.Namespace, Name: serverId}, serverManager)
if err != nil {
return nil, err
}
return &models.FileBrowser{ServerId: serverManager.Name, Id: serverManager.Name, Url: serverManager.Status.Browser.Url}, nil
}
func (i *InstanceManager) GetFileBrowserFromUrl(ctx context.Context, url string) (*models.FileBrowser, error) {
serverManager := &servermanagerv1.ServerManager{}
urlSegments := strings.Split(url, "/")
namespace, serverId := urlSegments[1], urlSegments[2]
err := i.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: serverId}, serverManager)
if err != nil {
return nil, err
}
return &models.FileBrowser{ServerId: serverManager.Name, Id: serverManager.Name, Url: serverManager.Status.Browser.Url}, nil
}
func (i *InstanceManager) ListFileBrowsers(ctx context.Context) ([]models.FileBrowser, error) {
serverManagers := &servermanagerv1.ServerManagerList{}
err := i.client.List(ctx, serverManagers, &client.ListOptions{Namespace: i.Config.Namespace})
if err != nil {
return nil, err
}
fileBrowsers := make([]models.FileBrowser, len(serverManagers.Items))
for i, serverManager := range serverManagers.Items {
fileBrowsers[i] = models.FileBrowser{ServerId: serverManager.Name, Id: serverManager.Name, Url: serverManager.Status.Browser.Url}
}
return fileBrowsers, nil
}
// Status Changing
func (i *InstanceManager) StartFileBrowser(ctx context.Context, serverId string) (*models.FileBrowser, error) {
serverManager := &servermanagerv1.ServerManager{}
err := i.client.Get(ctx, client.ObjectKey{Namespace: i.Config.Namespace, Name: serverId}, serverManager)
if err != nil {
return nil, err
}
serverManager.Spec.Browser.On = true
err = i.client.Update(ctx, serverManager)
if err != nil {
return nil, err
}
childContext, cancelFunc := context.WithTimeout(ctx, time.Second*30)
defer cancelFunc()
for !serverManager.Status.Browser.Running {
err = i.client.Get(childContext, client.ObjectKey{Name: serverId, Namespace: i.Config.Namespace}, serverManager)
if err != nil {
return nil, err
}
}
return &models.FileBrowser{Url: serverManager.Status.Browser.Url, Id: serverManager.Name, ServerId: serverManager.Name}, nil
}
func (i *InstanceManager) StopFileBrowser(ctx context.Context, serverId string) error {
serverManager := &servermanagerv1.ServerManager{}
err := i.client.Get(ctx, client.ObjectKey{Namespace: i.Config.Namespace, Name: serverId}, serverManager)
if err != nil {
return err
}
serverManager.Spec.Browser.On = false
err = i.client.Update(ctx, serverManager)
if err != nil {
return err
}
return nil
}
func NewInstanceManager(config models.KubernetesInstanceManagerConfig) (*InstanceManager, error) {
c, err := rest.InClusterConfig()
if err != nil {
kubeconfig := filepath.Join(os.Getenv("HOME"), ".kube", "config")
c, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, err
}
}
scheme := runtime.NewScheme()
clientgoscheme.AddToScheme(scheme)
servermanagerv1.AddToScheme(scheme)
client, err := client.New(c, client.Options{Scheme: scheme})
if err != nil {
return nil, err
}
coreV1Cli, err := kubernetesclient.NewForConfig(c)
if err != nil {
return nil, err
}
return &InstanceManager{
client: client,
coreV1Cli: coreV1Cli,
restCfg: c,
Config: config,
}, nil
}