package controllers import ( "context" "errors" "fmt" "log" "github.com/google/uuid" "github.com/joho/godotenv" "git.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/kube" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" ) type Environemnt struct { Controller ctrl.Manager UserID string Data *EnvironemntData Token string } type EnvironemntData struct { UUID string Name string Description string Provider string Kubernetes string Location string ServerType string } func (e *EnvironemntData) buildVars() (string, error) { vars := fmt.Sprintf(`# -- Generated by the softplayer controller SP_PROVIDER=%s SP_KUBERNETES=%s SP_SERVER_TYPE=%s SP_SERVER_LOCATION=%s`, e.Provider, e.Kubernetes, e.ServerType, e.Location, ) return vars, nil } func (env *Environemnt) isNsVerified(ctx context.Context) error { client := env.Controller.GetClient() ns := &corev1.Namespace{} if err := client.Get(ctx, types.NamespacedName{Name: env.UserID}, ns); err != nil { return err } val, ok := ns.GetLabels()["email-verified"] if !ok || val == "false" { return errors.New("user email is not verified, can't create an new env") } return nil } // Create environment should create a new configmap in the user's namespace // using a token that belongs to the user. func (env *Environemnt) Create(ctx context.Context) error { env.Data.UUID = uuid.New().String() if err := env.isNsVerified(ctx); err != nil { log.Println("Can't verify ns") return err } env.Controller.GetClient() conf := &rest.Config{ Host: "https://kubernetes.default.svc.cluster.local:443", BearerToken: env.Token, TLSClientConfig: rest.TLSClientConfig{ Insecure: true, }, } controller, err := ctrl.NewManager(conf, ctrl.Options{}) if err != nil { return err } vars, err := env.Data.buildVars() if err != nil { return err } obj := corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: env.Data.UUID, Namespace: env.UserID, Labels: map[string]string{ "component": "bootstrap", "kind": "environment", }, }, Data: map[string]string{ "name": env.Data.Name, "description": env.Data.Description, "vars": vars, }, } if err := kube.Create(ctx, controller.GetClient(), &obj, false); err != nil { return err } return nil } func (env *Environemnt) Update(ctx context.Context) error { if err := env.isNsVerified(ctx); err != nil { log.Println("Can't verify ns") return err } env.Controller.GetClient() conf := &rest.Config{ Host: "https://kubernetes.default.svc.cluster.local:443", BearerToken: env.Token, TLSClientConfig: rest.TLSClientConfig{ Insecure: true, }, } controller, err := ctrl.NewManager(conf, ctrl.Options{}) if err != nil { return err } oldEnv := &Environemnt{ Controller: env.Controller, UserID: env.UserID, Token: env.Token, Data: &EnvironemntData{ UUID: env.Data.UUID, }, } if err := oldEnv.Get(ctx); err != nil { return err } // Check whter immutable fields are changed if oldEnv.Data.Provider != env.Data.Provider { return errors.New("provider can't be changed") } if oldEnv.Data.Location != env.Data.Location { return errors.New("location can't be changed") } vars, err := env.Data.buildVars() if err != nil { return err } obj := corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: env.Data.UUID, Namespace: env.UserID, Labels: map[string]string{ "component": "bootstrap", "kind": "environment", }, }, Data: map[string]string{ "name": env.Data.Name, "description": env.Data.Description, "vars": vars, }, } if err := kube.Update(ctx, controller.GetClient(), &obj); err != nil { return err } return nil } func (env *Environemnt) Delete(ctx context.Context) error { env.Controller.GetClient() conf := &rest.Config{ Host: "https://kubernetes.default.svc.cluster.local:443", BearerToken: env.Token, TLSClientConfig: rest.TLSClientConfig{ Insecure: true, }, } controller, err := ctrl.NewManager(conf, ctrl.Options{}) if err != nil { return err } obj := corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: env.Data.UUID, Namespace: env.UserID, Labels: map[string]string{ "component": "bootstrap", }, }, } if err := kube.Delete(ctx, controller.GetClient(), &obj, false); err != nil { return err } return nil } func (env *Environemnt) ListEnvs(ctx context.Context) ([]*Environemnt, error) { env.Controller.GetClient() conf := &rest.Config{ Host: "https://kubernetes.default.svc.cluster.local:443", BearerToken: env.Token, TLSClientConfig: rest.TLSClientConfig{ Insecure: true, }, } clientset, err := kubernetes.NewForConfig(conf) if err != nil { return nil, err } configmaps, err := clientset.CoreV1().ConfigMaps(env.UserID).List(ctx, metav1.ListOptions{LabelSelector: "kind=environment"}) if err != nil { return nil, err } result := []*Environemnt{} for _, cm := range configmaps.Items { i := &Environemnt{} data := &EnvironemntData{ UUID: cm.GetName(), } i.Token = env.Token i.UserID = env.UserID i.Data = data i.Controller = env.Controller if err := i.Get(ctx); err != nil { return nil, err } result = append(result, i) } return result, nil } func (env *Environemnt) Get(ctx context.Context) error { env.Controller.GetClient() conf := &rest.Config{ Host: "https://kubernetes.default.svc.cluster.local:443", BearerToken: env.Token, TLSClientConfig: rest.TLSClientConfig{ Insecure: true, }, } clientset, err := kubernetes.NewForConfig(conf) if err != nil { return err } envData, err := clientset.CoreV1().ConfigMaps(env.UserID).Get(ctx, env.Data.UUID, metav1.GetOptions{}) if err != nil { return err } res, err := godotenv.Unmarshal(envData.Data["vars"]) if err != nil { return err } if val, ok := envData.Data["name"]; ok { env.Data.Name = val } else { env.Data.Name = "" } if val, ok := envData.Data["description"]; ok { env.Data.Description = val } else { env.Data.Description = "" } if val, ok := res["SP_PROVIDER"]; ok { env.Data.Provider = val } else { env.Data.Provider = "" } if val, ok := res["SP_KUBERNETES"]; ok { env.Data.Kubernetes = val } else { env.Data.Kubernetes = "" } if val, ok := res["SP_SERVER_TYPE"]; ok { env.Data.ServerType = val } else { env.Data.ServerType = "" } if val, ok := res["SP_SERVER_LOCATION"]; ok { env.Data.Location = val } else { env.Data.Location = "" } return nil }