Skip to content

Commit

Permalink
feat: exec added.
Browse files Browse the repository at this point in the history
  • Loading branch information
beneiltis committed Oct 6, 2023
1 parent 6b75081 commit 9266eca
Show file tree
Hide file tree
Showing 17 changed files with 325 additions and 23 deletions.
1 change: 1 addition & 0 deletions cmd/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var operatorCmd = &cobra.Command{
kubernetes.ContextAddMany(contexts)

go operator.InitBackend()
go operator.InitWebsocket()
operator.InitFrontend()
},
}
Expand Down
4 changes: 3 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ var rootCmd = &cobra.Command{
os.Exit(0)
}

utils.InitConfigYaml(debug, customConfig, stage)
if cmd.CommandPath() != "punq system reset-config" {
utils.InitConfigYaml(debug, customConfig, stage)
}

if !utils.ContainsEqual(cmdsWithoutContext, cmd.CommandPath()) {
mokubernetes.InitKubernetes(utils.CONFIG.Kubernetes.RunInCluster)
Expand Down
30 changes: 24 additions & 6 deletions cmd/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"fmt"
"strings"

"github.com/mogenius/punq/dtos"
"github.com/mogenius/punq/services"
Expand Down Expand Up @@ -31,25 +32,42 @@ var addUserCmd = &cobra.Command{
Short: "Add punq user.",
Long: `The add command lets you add a user into punq.`,
Run: func(cmd *cobra.Command, args []string) {
RequireStringFlag(email, "email")
RequireStringFlag(displayName, "displayname")
RequireStringFlag(password, "password")

selectedAccess := dtos.READER // default level
if accessLevel != "" {
selectedAccess = dtos.AccessLevelFromString(accessLevel)
} else {
selectedAccess = dtos.ADMIN
}

firstname := utils.RandomFirstName()
middlename := utils.RandomMiddleName()
lastname := utils.RandomLastName()

if email == "" {
email = fmt.Sprintf("%s-%[email protected]", strings.ToLower(firstname), strings.ToLower(lastname))
}

_, err := services.AddUser(dtos.PunqUserCreateInput{
if password == "" {
password = utils.NanoId()
}

if displayName == "" {
displayName = fmt.Sprintf("%s %s %s", firstname, middlename, lastname)
}

newUser := dtos.PunqUserCreateInput{
Email: email,
Password: password,
DisplayName: displayName,
AccessLevel: selectedAccess,
})
}

_, err := services.AddUser(newUser)
if err != nil {
utils.FatalError(err.Error())
} else {
utils.PrintInfo("User added succesfully ✅.")
structs.PrettyPrint(newUser)
}
},
}
Expand Down
6 changes: 5 additions & 1 deletion config/local.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
config:
version: 1
version: 2

backend:
host: 127.0.0.1
Expand All @@ -8,6 +8,10 @@ backend:
frontend:
host: 127.0.0.1
port: 8081

websocket:
host: 127.0.0.1
port: 8082

kubernetes:
cluster_name: your-cluster-name
Expand Down
7 changes: 6 additions & 1 deletion config/operator.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
config:
version: 1
version: 2

backend:
host: 127.0.0.1
Expand All @@ -9,6 +9,11 @@ frontend:
host: 127.0.0.1
port: 8081

websocket:
host: 127.0.0.1
port: 8082


kubernetes:
cluster_name: your-cluster-name
own_namespace: punq
Expand Down
6 changes: 5 additions & 1 deletion config/prod.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
config:
version: 1
version: 2

backend:
host: 127.0.0.1
Expand All @@ -9,6 +9,10 @@ frontend:
host: 127.0.0.1
port: 8081

websocket:
host: 127.0.0.1
port: 8082

kubernetes:
cluster_name: your-cluster-name
own_namespace: punq
Expand Down
11 changes: 5 additions & 6 deletions kubernetes/addResources.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ func Deploy(clusterName string, ingressHostname string) {
addRbac(provider)
addDeployment(provider)

// _, err = CreateClusterSecretIfNotExist(provider)
// if err != nil {
// logger.Log.Fatalf("Error creating cluster secret. Aborting: %s.", err.Error())
// }

_, err = CreateContextSecretIfNotExist(provider)
if err != nil {
logger.Log.Fatalf("Error creating context secret. Aborting: %s.", err.Error())
Expand Down Expand Up @@ -69,6 +64,10 @@ func addService(provider *KubeProvider) {
punqService.Spec.Ports[1].Protocol = core.ProtocolTCP
punqService.Spec.Ports[1].Port = int32(utils.CONFIG.Frontend.Port)
punqService.Spec.Ports[1].TargetPort = intstr.Parse(fmt.Sprint(utils.CONFIG.Frontend.Port))
punqService.Spec.Ports[2].Name = fmt.Sprintf("%d-%s-websocket", utils.CONFIG.Websocket.Port, SERVICENAME)
punqService.Spec.Ports[2].Protocol = core.ProtocolTCP
punqService.Spec.Ports[2].Port = int32(utils.CONFIG.Websocket.Port)
punqService.Spec.Ports[2].TargetPort = intstr.Parse(fmt.Sprint(utils.CONFIG.Websocket.Port))
punqService.Spec.Selector["app"] = version.Name

serviceClient := provider.ClientSet.CoreV1().Services(utils.CONFIG.Kubernetes.OwnNamespace)
Expand Down Expand Up @@ -331,7 +330,7 @@ func addDeployment(provider *KubeProvider) {
deploymentContainer.WithName(version.Name)
deploymentContainer.WithImage(version.OperatorImage)

deploymentContainer.WithPorts(applyconfcore.ContainerPort().WithContainerPort(int32(utils.CONFIG.Backend.Port)).WithContainerPort(int32(utils.CONFIG.Frontend.Port)))
deploymentContainer.WithPorts(applyconfcore.ContainerPort().WithContainerPort(int32(utils.CONFIG.Backend.Port)).WithContainerPort(int32(utils.CONFIG.Frontend.Port)).WithContainerPort(int32(utils.CONFIG.Websocket.Port)))

envVars := []applyconfcore.EnvVarApplyConfiguration{}
envVars = append(envVars, applyconfcore.EnvVarApplyConfiguration{
Expand Down
4 changes: 2 additions & 2 deletions kubernetes/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func ExecTest() error {
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr

go sendData(cmdStdin, cmdStdout)
go SendData(cmdStdin, cmdStdout)

// Run the command
err := cmd.Run()
Expand Down Expand Up @@ -84,7 +84,7 @@ func ExecTest() error {
// return err
// }

func sendData(cmdStdin io.WriteCloser, cmdStdout io.ReadCloser) {
func SendData(cmdStdin io.WriteCloser, cmdStdout io.ReadCloser) {
// Create a dialer
dialer := websocket.DefaultDialer

Expand Down
20 changes: 19 additions & 1 deletion operator/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func InitFrontend() {
c.Data(http.StatusOK, "text/html; charset=utf-8", getIndexHtml())
})

utils.PrintInfo(fmt.Sprintf("Frontend started: http://%s:%d", utils.CONFIG.Frontend.Host, utils.CONFIG.Frontend.Port))
err := router.Run(fmt.Sprintf(":%d", utils.CONFIG.Frontend.Port))
logger.Log.Errorf("Frontend (gin) stopped with error: %s", err.Error())
}
Expand All @@ -62,10 +63,27 @@ func InitBackend() {
InitGeneralRoutes(router)
InitWorkloadRoutes(router)

utils.PrintInfo(fmt.Sprintf("Backend started: http://%s:%d", utils.CONFIG.Backend.Host, utils.CONFIG.Backend.Port))
err := router.Run(fmt.Sprintf(":%d", utils.CONFIG.Backend.Port))
logger.Log.Errorf("Operator (gin) stopped with error: %s", err.Error())
}

func InitWebsocket() {
gin.SetMode(gin.ReleaseMode)
router := gin.New()
config := cors.DefaultConfig()
config.AllowAllOrigins = true

router.Use(cors.New(config))
router.Use(CreateLogger("WEBSOCK"))

InitWebsocketRoutes(router)

utils.PrintInfo(fmt.Sprintf("Websocket started: ws://%s:%d", utils.CONFIG.Websocket.Host, utils.CONFIG.Websocket.Port))
err := router.Run(fmt.Sprintf(":%d", utils.CONFIG.Websocket.Port))
logger.Log.Errorf("Websocket (gin) stopped with error: %s", err.Error())
}

func embedFs() http.FileSystem {
sub, err := fs.Sub(HtmlDirFs, "ui/dist")
if err != nil {
Expand All @@ -80,7 +98,7 @@ func embedFs() http.FileSystem {
if len(dirContent) <= 0 {
panic("dist folder empty. Cannnot serve site. FATAL.")
} else {
logger.Log.Noticef("Loaded %d static files from embed.", len(dirContent))
fmt.Printf("Loaded %d static files from embed.\n", len(dirContent))
}
return http.FS(sub)
}
Expand Down
39 changes: 39 additions & 0 deletions operator/context-middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,42 @@ func RequireContextId() gin.HandlerFunc {
}
}
}

func RequireNamespace() gin.HandlerFunc {
return func(c *gin.Context) {
contextId := services.GetGinNamespace(c)
if contextId == nil {
utils.MissingHeader(c, "X-Namespace")
c.Abort()
return
} else {
c.Next()
}
}
}

func RequirePodName() gin.HandlerFunc {
return func(c *gin.Context) {
contextId := services.GetGinPodname(c)
if contextId == nil {
utils.MissingHeader(c, "X-Podname")
c.Abort()
return
} else {
c.Next()
}
}
}

func RequireContainerName() gin.HandlerFunc {
return func(c *gin.Context) {
contextId := services.GetGinContainername(c)
if contextId == nil {
utils.MissingHeader(c, "X-Container")
c.Abort()
return
} else {
c.Next()
}
}
}
101 changes: 101 additions & 0 deletions operator/routes-websocket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package operator

import (
"bufio"
"fmt"
"log"
"net/http"
"os"
"os/exec"

"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/mogenius/punq/dtos"
"github.com/mogenius/punq/services"
"github.com/mogenius/punq/utils"
)

func InitWebsocketRoutes(router *gin.Engine) {
router.GET("/exec-sh", Auth(dtos.ADMIN), RequireNamespace(), RequirePodName(), RequireContainerName(), connectWs)
}

var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true // adjust to implement your origin validation logic
},
}

func connectWs(c *gin.Context) {
namespace := services.GetGinNamespace(c)
container := services.GetGinContainername(c)
podName := services.GetGinPodname(c)

log.Printf("exec-sh: %s %s %s\n", *namespace, *container, *podName)

ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Printf("Failed to upgrade ws: %+v", err)
return
}
defer func() {
ws.Close()
}()

cmd := exec.Command("sh", "-c", fmt.Sprintf("kubectl exec -i --tty -c %s -n %s %s -- /bin/sh", *container, *namespace, *podName))
cmd.Env = os.Environ()
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal("Error creating stdin pipe:", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal("Error creating stdout pipe:", err)
}

go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
data := scanner.Bytes()
if utils.CONFIG.Misc.Debug {
fmt.Printf("Response-Line: '%s'\n", string(data))
}
err = ws.WriteMessage(websocket.TextMessage, data)
if err != nil {
log.Printf("Error writing to ws: %+v", err)
}
}
}()

go func() {
for {
_, msg, err := ws.ReadMessage()
if utils.CONFIG.Misc.Debug {
fmt.Printf("Received Cmd: '%s'", string(msg))
}
msg = append(msg, '\n')
if err != nil {
log.Printf("Error reading from ws: %+v", err)
log.Printf("CLOSE: exec-sh: %s %s %s\n", *namespace, *container, *podName)
break
}
_, err = stdin.Write(msg)
if err != nil {
log.Printf("Error writing to stdin: %+v", err)
}
}
}()

err = cmd.Start()
if err != nil {
log.Printf("Error starting cmd: %+v", err)
return
}

err = cmd.Wait()
if err != nil {
log.Printf("Cmd returned error: %+v", err.Error())
return
}
}
21 changes: 21 additions & 0 deletions services/context-service.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,27 @@ func GetGinContextId(c *gin.Context) *string {
return nil
}

func GetGinNamespace(c *gin.Context) *string {
if namespace := c.GetHeader("X-Namespace"); namespace != "" {
return &namespace
}
return nil
}

func GetGinPodname(c *gin.Context) *string {
if podname := c.GetHeader("X-Podname"); podname != "" {
return &podname
}
return nil
}

func GetGinContainername(c *gin.Context) *string {
if container := c.GetHeader("X-Container"); container != "" {
return &container
}
return nil
}

// func GetGinContextContexts(c *gin.Context) *[]dtos.PunqContext {
// if contextArray, exists := c.Get("contexts"); exists {
// contexts, ok := contextArray.([]dtos.PunqContext)
Expand Down
Loading

0 comments on commit 9266eca

Please sign in to comment.