All checks were successful
Build and Push Docker Image / Build image (push) Successful in 1m43s
388 lines
11 KiB
Go
388 lines
11 KiB
Go
package kubernetes
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"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("server-")
|
|
|
|
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.Delete(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").
|
|
Namespace(i.Config.Namespace).
|
|
Name(serverId).
|
|
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()
|
|
_ = executor.StreamWithContext(ctx, remotecommand.StreamOptions{Stdin: stdinReader, Stdout: stdoutWriter, Stderr: stdoutWriter, Tty: true, TerminalSizeQueue: queue})
|
|
}()
|
|
|
|
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) 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
|
|
}
|