@@ -11,6 +11,7 @@ import (
1111 "log"
1212 "net/http"
1313 "reflect"
14+ "strings"
1415 "sync"
1516 "time"
1617
@@ -487,6 +488,11 @@ func (c *Controller) handleList(w http.ResponseWriter, r *http.Request) {
487488 if appFilter != "" {
488489 q = q .Where ("name = ?" , appFilter )
489490 }
491+ if projectFilter != "" {
492+ q = q .Where ("metadata_project = ?" , projectFilter )
493+ } else if ! allProjects {
494+ q = q .Where ("metadata_project IS NULL OR metadata_project = ''" )
495+ }
490496 if err := q .Find (& apps ).Error ; err != nil {
491497 http .Error (w , "failed to list applications" , http .StatusInternalServerError )
492498 return
@@ -506,6 +512,29 @@ func (c *Controller) handleList(w http.ResponseWriter, r *http.Request) {
506512 if appFilter != "" {
507513 q = q .Where ("application = ?" , appFilter )
508514 }
515+ if projectFilter != "" {
516+ var appNames []string
517+ if err := db .DB .Model (& v1.Application {}).Where ("metadata_project = ?" , projectFilter ).Pluck ("name" , & appNames ).Error ; err != nil {
518+ http .Error (w , "failed to filter components by project" , http .StatusInternalServerError )
519+ return
520+ }
521+ if len (appNames ) > 0 {
522+ q = q .Where ("application IN ?" , appNames )
523+ } else {
524+ q = q .Where ("(application IS NULL OR application = '') AND metadata_project = ?" , projectFilter )
525+ }
526+ } else if ! allProjects {
527+ var appNames []string
528+ if err := db .DB .Model (& v1.Application {}).Where ("metadata_project IS NULL OR metadata_project = ''" ).Pluck ("name" , & appNames ).Error ; err != nil {
529+ http .Error (w , "failed to filter components by default project scope" , http .StatusInternalServerError )
530+ return
531+ }
532+ if len (appNames ) > 0 {
533+ q = q .Where ("application IN ? OR ((application IS NULL OR application = '') AND (metadata_project IS NULL OR metadata_project = ''))" , appNames )
534+ } else {
535+ q = q .Where ("(application IS NULL OR application = '') AND (metadata_project IS NULL OR metadata_project = '')" )
536+ }
537+ }
509538 if err := q .Find (& comps ).Error ; err != nil {
510539 http .Error (w , "failed to list components" , http .StatusInternalServerError )
511540 return
@@ -516,7 +545,7 @@ func (c *Controller) handleList(w http.ResponseWriter, r *http.Request) {
516545 w .Write (b )
517546 return
518547 case "VirtualMachine" :
519- if r .URL .Query ().Get ("all-vms" ) == "true" || projectFilter != "" || allProjects {
548+ if r .URL .Query ().Get ("all-vms" ) == "true" {
520549 payload := map [string ]interface {}{"kind" : "VirtualMachine" }
521550 if name != "" {
522551 payload ["name" ] = name
@@ -549,6 +578,29 @@ func (c *Controller) handleList(w http.ResponseWriter, r *http.Request) {
549578 if appFilter != "" {
550579 q = q .Where ("application = ?" , appFilter )
551580 }
581+ if projectFilter != "" {
582+ var appNames []string
583+ if err := db .DB .Model (& v1.Application {}).Where ("metadata_project = ?" , projectFilter ).Pluck ("name" , & appNames ).Error ; err != nil {
584+ http .Error (w , "failed to filter virtualmachines by project" , http .StatusInternalServerError )
585+ return
586+ }
587+ if len (appNames ) > 0 {
588+ q = q .Where ("application IN ? OR ((application IS NULL OR application = '') AND metadata_project = ?)" , appNames , projectFilter )
589+ } else {
590+ q = q .Where ("(application IS NULL OR application = '') AND metadata_project = ?" , projectFilter )
591+ }
592+ } else if ! allProjects {
593+ var appNames []string
594+ if err := db .DB .Model (& v1.Application {}).Where ("metadata_project IS NULL OR metadata_project = ''" ).Pluck ("name" , & appNames ).Error ; err != nil {
595+ http .Error (w , "failed to filter virtualmachines by default project scope" , http .StatusInternalServerError )
596+ return
597+ }
598+ if len (appNames ) > 0 {
599+ q = q .Where ("application IN ? OR ((application IS NULL OR application = '') AND (metadata_project IS NULL OR metadata_project = ''))" , appNames )
600+ } else {
601+ q = q .Where ("(application IS NULL OR application = '') AND (metadata_project IS NULL OR metadata_project = '')" )
602+ }
603+ }
552604 if err := q .Find (& vms ).Error ; err != nil {
553605 http .Error (w , "failed to list virtualmachines" , http .StatusInternalServerError )
554606 return
@@ -940,6 +992,23 @@ func (c *Controller) Apply(resource interface{}) error {
940992
941993// applyApplication creates/updates an Application resource
942994func (c * Controller ) applyApplication (app * v1.Application ) error {
995+ // Accept project in either metadata.project or spec.project and keep them in sync.
996+ appProject := strings .TrimSpace (app .Metadata .Project )
997+ if appProject == "" {
998+ appProject = strings .TrimSpace (app .Spec .Project )
999+ }
1000+ if appProject != "" {
1001+ app .Metadata .Project = appProject
1002+ app .Spec .Project = appProject
1003+ }
1004+
1005+ // Upsert: look up an existing record by name so Save() performs an UPDATE
1006+ // rather than an INSERT (Save with ID=0 always inserts a new row).
1007+ var existing v1.Application
1008+ if err := db .DB .Where ("name = ?" , app .Metadata .Name ).First (& existing ).Error ; err == nil {
1009+ app .Model = existing .Model
1010+ }
1011+
9431012 // Save desired state to database and mark as Starting.
9441013 if app .Status .ObservedState == "" {
9451014 app .Status .ObservedState = "Starting"
@@ -963,14 +1032,19 @@ func (c *Controller) applyApplication(app *v1.Application) error {
9631032// referenced by the Application if they do not already exist. It also ensures
9641033// the Component.Application field is set to the owning application name.
9651034func ensureComponentsForApplication (app * v1.Application ) error {
1035+ appProject := strings .TrimSpace (app .Metadata .Project )
1036+ if appProject == "" {
1037+ appProject = strings .TrimSpace (app .Spec .Project )
1038+ }
1039+
9661040 for _ , cref := range app .Spec .Components {
9671041 var comp v1.Component
9681042 if err := db .DB .Where ("name = ?" , cref .Name ).First (& comp ).Error ; err != nil {
9691043 // component not found: create a new record
9701044 comp = v1.Component {
9711045 APIVersion : v1 .APIVersion ,
9721046 Kind : "Component" ,
973- Metadata : v1.Metadata {Name : cref .Name },
1047+ Metadata : v1.Metadata {Name : cref .Name , Project : appProject },
9741048 Spec : v1.ComponentSpec {
9751049 VirtualMachineSpec : cref .VirtualMachineSpec ,
9761050 Replicas : cref .Replicas ,
@@ -1011,14 +1085,37 @@ func ensureComponentsForApplication(app *v1.Application) error {
10111085 }
10121086 }
10131087
1088+ if appProject != "" && comp .Metadata .Project != appProject {
1089+ if err := db .DB .Model (& v1.Component {}).Where ("name = ?" , comp .Metadata .Name ).Update ("metadata_project" , appProject ).Error ; err != nil {
1090+ log .Printf ("failed to set component %s project to %s: %v" , comp .Metadata .Name , appProject , err )
1091+ return err
1092+ }
1093+ comp .Metadata .Project = appProject
1094+ }
1095+
10141096 // Also ensure existing VM records for this component are linked
10151097 // to the owning application if they don't already have an owner.
10161098 var vms []v1.VirtualMachine
10171099 if err := db .DB .Where ("component = ?" , comp .Metadata .Name ).Find (& vms ).Error ; err == nil {
10181100 for _ , vm := range vms {
1101+ changed := false
10191102 if vm .Application == "" {
10201103 log .Printf ("Linking VM %s to application %s" , vm .Metadata .Name , app .Metadata .Name )
1021- if err := db .DB .Model (& v1.VirtualMachine {}).Where ("name = ?" , vm .Metadata .Name ).Update ("application" , app .Metadata .Name ).Error ; err != nil {
1104+ vm .Application = app .Metadata .Name
1105+ changed = true
1106+ }
1107+ if appProject != "" {
1108+ if vm .Metadata .Project != appProject {
1109+ vm .Metadata .Project = appProject
1110+ changed = true
1111+ }
1112+ if vm .Spec .Project != appProject {
1113+ vm .Spec .Project = appProject
1114+ changed = true
1115+ }
1116+ }
1117+ if changed {
1118+ if err := db .DB .Save (& vm ).Error ; err != nil {
10221119 return err
10231120 }
10241121 }
@@ -1061,6 +1158,8 @@ func (c *Controller) applyComponent(comp *v1.Component) error {
10611158 if existing .Application != "" && comp .Application == "" {
10621159 comp .Application = existing .Application
10631160 }
1161+ // Use the existing record's primary key so Save() performs UPDATE
1162+ comp .Model = existing .Model
10641163 }
10651164
10661165 // Persist desired component with effective spec
0 commit comments