Skip to content

Commit b8a604d

Browse files
authored
Merge pull request #8 from cpp-cyber/development
Development
2 parents 8b6e957 + a98faac commit b8a604d

9 files changed

Lines changed: 226 additions & 49 deletions

File tree

internal/api/handlers/cloning_handler.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/cpp-cyber/proclone/internal/ldap"
1212
"github.com/cpp-cyber/proclone/internal/proxmox"
1313
"github.com/cpp-cyber/proclone/internal/tools"
14+
"github.com/cpp-cyber/proclone/internal/tools/sse"
1415
"github.com/gin-contrib/sessions"
1516
"github.com/gin-gonic/gin"
1617
)
@@ -88,16 +89,54 @@ func (ch *CloningHandler) CloneTemplateHandler(c *gin.Context) {
8889
return
8990
}
9091

92+
// Check for existing deployments before starting SSE
93+
targetPoolName := fmt.Sprintf("%s_%s", req.Template, username)
94+
isValid, err := ch.Service.ValidateCloneRequest(targetPoolName, username)
95+
if err != nil {
96+
log.Printf("Error validating deployment for user %s: %v", username, err)
97+
c.JSON(http.StatusInternalServerError, gin.H{
98+
"error": "Failed to validate existing deployments",
99+
"details": err.Error(),
100+
})
101+
return
102+
}
103+
if !isValid {
104+
log.Printf("Template %s is already deployed for user %s or they have exceeded deployment limits", req.Template, username)
105+
c.JSON(http.StatusConflict, gin.H{
106+
"error": "Deployment not allowed",
107+
"details": fmt.Sprintf("Template %s is already deployed for %s or they have exceeded the maximum of 5 deployed pods", req.Template, username),
108+
})
109+
return
110+
}
111+
112+
// Create new sse object for streaming
113+
sseWriter, err := sse.NewWriter(c.Writer)
114+
if err != nil {
115+
c.JSON(http.StatusInternalServerError, gin.H{
116+
"error": "Failed to initialize SSE",
117+
"details": err.Error(),
118+
})
119+
return
120+
}
121+
122+
sseWriter.Send(
123+
cloning.ProgressMessage{
124+
Message: "Retrieving template information",
125+
Progress: 0,
126+
},
127+
)
128+
91129
// Create the cloning request using the new format
92130
cloneReq := cloning.CloneRequest{
93131
Template: req.Template,
94-
CheckExistingDeployments: true, // Check for existing deployments for single user clones
132+
CheckExistingDeployments: false, // Already checked above
95133
Targets: []cloning.CloneTarget{
96134
{
97135
Name: username,
98136
IsGroup: false,
99137
},
100138
},
139+
SSE: sseWriter,
101140
}
102141

103142
if err := ch.Service.CloneTemplate(cloneReq); err != nil {
@@ -144,16 +183,27 @@ func (ch *CloningHandler) AdminCloneTemplateHandler(c *gin.Context) {
144183
})
145184
}
146185

186+
// Create new sse object for streaming
187+
sseWriter, err := sse.NewWriter(c.Writer)
188+
if err != nil {
189+
c.JSON(http.StatusInternalServerError, gin.H{
190+
"error": "Failed to initialize SSE",
191+
"details": err.Error(),
192+
})
193+
return
194+
}
195+
147196
// Create clone request
148197
cloneReq := cloning.CloneRequest{
149198
Template: req.Template,
150199
Targets: targets,
151200
CheckExistingDeployments: false,
152201
StartingVMID: req.StartingVMID,
202+
SSE: sseWriter,
153203
}
154204

155205
// Perform clone operation
156-
err := ch.Service.CloneTemplate(cloneReq)
206+
err = ch.Service.CloneTemplate(cloneReq)
157207
if err != nil {
158208
log.Printf("Admin %s encountered error while bulk cloning template: %v", username, err)
159209
c.JSON(http.StatusInternalServerError, gin.H{
@@ -264,7 +314,7 @@ func (ch *CloningHandler) GetPodsHandler(c *gin.Context) {
264314

265315
// Loop through the user's deployed pods and add template information
266316
for i := range pods {
267-
templateName := strings.Replace(pods[i].Name[5:], fmt.Sprintf("_%s", username), "", 1)
317+
templateName := strings.Replace(strings.ToLower(pods[i].Name[5:]), fmt.Sprintf("_%s", strings.ToLower(username)), "", 1)
268318
templateInfo, err := ch.Service.DatabaseService.GetTemplateInfo(templateName)
269319
if err != nil {
270320
log.Printf("Error retrieving template info for pod %s: %v", pods[i].Name, err)

internal/api/handlers/dashboard_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func (dh *DashboardHandler) GetUserDashboardStatsHandler(c *gin.Context) {
9191

9292
// Loop through the user's deployed pods and add template information
9393
for i := range pods {
94-
templateName := strings.Replace(pods[i].Name[5:], fmt.Sprintf("_%s", username), "", 1)
94+
templateName := strings.Replace(strings.ToLower(pods[i].Name[5:]), fmt.Sprintf("_%s", strings.ToLower(username)), "", 1)
9595
templateInfo, err := dh.cloningHandler.Service.DatabaseService.GetTemplateInfo(templateName)
9696
if err != nil {
9797
log.Printf("Error retrieving template info for pod %s: %v", pods[i].Name, err)

internal/api/middleware/authorization.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,14 @@ func Logout(c *gin.Context) {
6363

6464
func CORSMiddleware(fqdn string) gin.HandlerFunc {
6565
return func(c *gin.Context) {
66-
c.Writer.Header().Set("Content-Type", "application/json")
66+
c.Writer.Header().Set("Content-Type", "application/json; text/event-stream")
6767
c.Writer.Header().Set("Access-Control-Allow-Origin", fqdn)
6868
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
6969
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
7070
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
7171
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Origin")
72+
c.Writer.Header().Set("Cache-Control", "no-cache")
73+
c.Writer.Header().Set("Connection", "keep-alive")
7274

7375
if c.Request.Method == "OPTIONS" {
7476
c.AbortWithStatus(200)

internal/cloning/cloning_service.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"fmt"
66
"log"
77
"os"
8-
"strings"
8+
"regexp"
99
"time"
1010

1111
"github.com/cpp-cyber/proclone/internal/ldap"
@@ -85,11 +85,11 @@ func (cs *CloningService) CloneTemplate(req CloneRequest) error {
8585
// 3. Identify router and other VMs
8686
var router *proxmox.VM
8787
var templateVMs []proxmox.VM
88+
routerPattern := regexp.MustCompile(`(?i)(router|pfsense|vyos)`)
8889

8990
for _, vm := range templatePool {
9091
// Check to see if this VM is the router
91-
lowerVMName := strings.ToLower(vm.Name)
92-
if strings.Contains(lowerVMName, "router") || strings.Contains(lowerVMName, "pfsense") {
92+
if routerPattern.MatchString(vm.Name) {
9393
router = &proxmox.VM{
9494
Name: vm.Name,
9595
Node: vm.NodeName,
@@ -166,6 +166,13 @@ func (cs *CloningService) CloneTemplate(req CloneRequest) error {
166166
}
167167

168168
// 7. Clone targets to proxmox
169+
req.SSE.Send(
170+
ProgressMessage{
171+
Message: "Cloning VMs",
172+
Progress: 10,
173+
},
174+
)
175+
169176
for _, target := range req.Targets {
170177
// Find best node per target
171178
bestNode, err := cs.ProxmoxService.FindBestNode()
@@ -186,9 +193,16 @@ func (cs *CloningService) CloneTemplate(req CloneRequest) error {
186193
if err != nil {
187194
errors = append(errors, fmt.Sprintf("failed to clone router VM for %s: %v", target.Name, err))
188195
} else {
196+
// Determine router type
197+
routerType, err := cs.getRouterType(*router)
198+
if err != nil {
199+
errors = append(errors, fmt.Sprintf("failed to get router type for %s: %v", target.Name, err))
200+
}
201+
189202
// Store router info for later operations
190203
clonedRouters = append(clonedRouters, RouterInfo{
191204
TargetName: target.Name,
205+
RouterType: routerType,
192206
PodNumber: target.PodNumber,
193207
Node: bestNode,
194208
VMID: target.VMIDs[0],
@@ -251,6 +265,12 @@ func (cs *CloningService) CloneTemplate(req CloneRequest) error {
251265
}
252266

253267
// 10. Start all routers and wait for them to be running
268+
req.SSE.Send(
269+
ProgressMessage{
270+
Message: "Starting routers",
271+
Progress: 25,
272+
},
273+
)
254274
log.Printf("Starting %d routers", len(clonedRouters))
255275
for _, routerInfo := range clonedRouters {
256276
// Wait for router disk to be available
@@ -278,6 +298,13 @@ func (cs *CloningService) CloneTemplate(req CloneRequest) error {
278298
}
279299

280300
// 11. Configure all pod routers (separate step after all routers are running)
301+
req.SSE.Send(
302+
ProgressMessage{
303+
Message: "Configuring pod routers",
304+
Progress: 33,
305+
},
306+
)
307+
281308
log.Printf("Configuring %d pod routers", len(clonedRouters))
282309
for _, routerInfo := range clonedRouters {
283310
// Double-check that router is still running before configuration
@@ -288,12 +315,20 @@ func (cs *CloningService) CloneTemplate(req CloneRequest) error {
288315
}
289316

290317
log.Printf("Configuring pod router for %s (Pod: %d, VMID: %d)", routerInfo.TargetName, routerInfo.PodNumber, routerInfo.VMID)
291-
err = cs.configurePodRouter(routerInfo.PodNumber, routerInfo.Node, routerInfo.VMID)
318+
err = cs.configurePodRouter(routerInfo.PodNumber, routerInfo.Node, routerInfo.VMID, routerInfo.RouterType)
292319
if err != nil {
293320
errors = append(errors, fmt.Sprintf("failed to configure pod router for %s: %v", routerInfo.TargetName, err))
294321
}
295322
}
296323

324+
// Router configuration complete - update progress
325+
req.SSE.Send(
326+
ProgressMessage{
327+
Message: "Finalizing deployment",
328+
Progress: 90,
329+
},
330+
)
331+
297332
// 12. Set permissions on the pool to the user/group
298333
for _, target := range req.Targets {
299334
err = cs.ProxmoxService.SetPoolPermission(target.PoolName, target.Name, target.IsGroup)
@@ -308,6 +343,14 @@ func (cs *CloningService) CloneTemplate(req CloneRequest) error {
308343
errors = append(errors, fmt.Sprintf("failed to increment template deployments for %s: %v", req.Template, err))
309344
}
310345

346+
// Final completion message
347+
req.SSE.Send(
348+
ProgressMessage{
349+
Message: "Template cloning completed!",
350+
Progress: 100,
351+
},
352+
)
353+
311354
// Handle errors and cleanup if necessary
312355
if len(errors) > 0 {
313356
cs.cleanupFailedClones(createdPools)
@@ -357,9 +400,7 @@ func (cs *CloningService) DeletePod(pod string) error {
357400
VMID: vm.VmId,
358401
})
359402
stoppedCount++
360-
} else {
361403
}
362-
} else {
363404
}
364405
}
365406

0 commit comments

Comments
 (0)