@@ -206,13 +206,62 @@ func (wt *watch) CreateProject(ctx context.Context, req *projectv1.CreateProject
206206 return storage .getProject (ctx , ptr .Name , ptr .Pointer , func (p * typesv1.Project ) error {
207207 out = p
208208 // Enrich project with all warnings
209- wt .enrichProjectWithWarnings (p , ptr )
209+ wt .enrichProjectWithWarnings (p . Status , ptr )
210210 return nil
211211 })
212212 })
213213 return & projectv1.CreateProjectResponse {Project : out }, err
214214}
215215
216+ func (wt * watch ) ValidateProject (ctx context.Context , req * projectv1.ValidateProjectRequest ) (* projectv1.ValidateProjectResponse , error ) {
217+ name := req .Spec .Name
218+
219+ var status * typesv1.ProjectStatus
220+ err := func () error {
221+ wt .mu .Lock ()
222+ defer wt .mu .Unlock ()
223+ cfg , err := wt .cfg .Reload ()
224+ if err != nil {
225+ return fmt .Errorf ("reading config file: %v" , err )
226+ }
227+ wt .cfg = cfg
228+ if cfg .Runtime == nil {
229+ cfg .Runtime = & typesv1.RuntimeConfig {}
230+ }
231+ if cfg .Runtime .ExperimentalFeatures == nil {
232+ cfg .Runtime .ExperimentalFeatures = & typesv1.RuntimeConfig_ExperimentalFeatures {}
233+ }
234+ if cfg .Runtime .ExperimentalFeatures .Projects == nil {
235+ cfg .Runtime .ExperimentalFeatures .Projects = & typesv1.ProjectsConfig {}
236+ }
237+ projects := cfg .Runtime .ExperimentalFeatures .Projects
238+
239+ sp := & typesv1.ProjectsConfig_Project {
240+ Name : name ,
241+ Pointer : req .Spec .Pointer ,
242+ }
243+
244+ storage , err := wt .storageForPointer (req .Spec .Pointer )
245+ if err != nil {
246+ return err
247+ }
248+ // Run the same validation as CreateProject
249+ if err := wt .validateProjectPointer (ctx , projects .Projects , name , sp , storage ); err != nil {
250+ return err
251+ }
252+
253+ // Create a status object to compute warnings using the same code path
254+ status = & typesv1.ProjectStatus {}
255+ wt .enrichProjectWithWarnings (status , sp )
256+ return nil
257+ }()
258+ if err != nil {
259+ return nil , err
260+ }
261+
262+ return & projectv1.ValidateProjectResponse {Status : status }, nil
263+ }
264+
216265func (wt * watch ) validateProjectPointer (ctx context.Context , projects []* typesv1.ProjectsConfig_Project , name string , sp * typesv1.ProjectsConfig_Project , storage projectStorage ) error {
217266 if name == "" {
218267 return connect .NewError (connect .CodeInvalidArgument , fmt .Errorf ("project name cannot be empty" ))
@@ -265,7 +314,7 @@ func (wt *watch) UpdateProject(ctx context.Context, req *projectv1.UpdateProject
265314 return storage .getProject (ctx , ptr .Name , ptr .Pointer , func (p * typesv1.Project ) error {
266315 out = p
267316 // Enrich project with all warnings
268- wt .enrichProjectWithWarnings (p , ptr )
317+ wt .enrichProjectWithWarnings (p . Status , ptr )
269318 return nil
270319 })
271320 })
@@ -311,7 +360,7 @@ func (wt *watch) GetProject(ctx context.Context, req *projectv1.GetProjectReques
311360 alertGroups = ag
312361
313362 // Enrich project with all warnings
314- wt .enrichProjectWithWarnings (p , ptr )
363+ wt .enrichProjectWithWarnings (p . Status , ptr )
315364
316365 return nil
317366 })
@@ -360,7 +409,7 @@ func (wt *watch) ListProject(ctx context.Context, req *projectv1.ListProjectRequ
360409 }
361410 return storage .getProject (ctx , sp .Name , sp .Pointer , func (p * typesv1.Project ) error {
362411 // Enrich project with all warnings
363- wt .enrichProjectWithWarnings (p , sp )
412+ wt .enrichProjectWithWarnings (p . Status , sp )
364413 out = append (out , & projectv1.ListProjectResponse_ListItem {Project : p })
365414 return nil
366415 })
@@ -720,33 +769,24 @@ func sharedAlertDirWarning(otherProjectName, dirPath string) string {
720769 return fmt .Sprintf ("Project %q shares the same alert directory (%s). Changes in one project will affect the other." , otherProjectName , dirPath )
721770}
722771
723- // enrichProjectWithWarnings populates all warnings for a project
772+ // enrichProjectWithWarnings populates all warnings for a project status
724773// This should be called whenever returning a project to ensure warnings are up-to-date
725- func (wt * watch ) enrichProjectWithWarnings (project * typesv1.Project , ptr * typesv1.ProjectsConfig_Project ) {
726- wt .addDirectoryConflictWarnings (project , ptr )
774+ func (wt * watch ) enrichProjectWithWarnings (status * typesv1.ProjectStatus , ptr * typesv1.ProjectsConfig_Project ) {
775+ wt .addDirectoryConflictWarnings (status , ptr )
727776 // Future warning checks can be added here
728777}
729778
730- // addDirectoryConflictWarnings checks if this project shares directories with other projects
731- // and adds warnings to the project status if conflicts are found
732- func (wt * watch ) addDirectoryConflictWarnings ( project * typesv1.Project , currentPtr * typesv1.ProjectsConfig_Project ) {
779+ // computeProjectWarnings computes warnings for a project without modifying it
780+ // Used by ValidateProject to preview warnings before creating the project
781+ func (wt * watch ) computeProjectWarnings ( currentPtr * typesv1.ProjectsConfig_Project , allProjects [] * typesv1.ProjectsConfig_Project ) [] string {
733782 // Only check for localhost projects
734783 localhost := currentPtr .Pointer .GetLocalhost ()
735784 if localhost == nil {
736- return
737- }
738-
739- cfg , err := wt .cfg .Reload ()
740- if err != nil {
741- return // Can't check, skip
742- }
743-
744- if cfg .Runtime == nil || cfg .Runtime .ExperimentalFeatures == nil || cfg .Runtime .ExperimentalFeatures .Projects == nil {
745- return
785+ return nil
746786 }
747787
748788 var conflicts []string
749- for _ , otherProj := range cfg . Runtime . ExperimentalFeatures . Projects . Projects {
789+ for _ , otherProj := range allProjects {
750790 if otherProj .Name == currentPtr .Name {
751791 continue // Skip self
752792 }
@@ -771,8 +811,24 @@ func (wt *watch) addDirectoryConflictWarnings(project *typesv1.Project, currentP
771811 }
772812 }
773813
814+ return conflicts
815+ }
816+
817+ // addDirectoryConflictWarnings checks if this project shares directories with other projects
818+ // and adds warnings to the project status if conflicts are found
819+ func (wt * watch ) addDirectoryConflictWarnings (status * typesv1.ProjectStatus , currentPtr * typesv1.ProjectsConfig_Project ) {
820+ cfg , err := wt .cfg .Reload ()
821+ if err != nil {
822+ return // Can't check, skip
823+ }
824+
825+ if cfg .Runtime == nil || cfg .Runtime .ExperimentalFeatures == nil || cfg .Runtime .ExperimentalFeatures .Projects == nil {
826+ return
827+ }
828+
829+ conflicts := wt .computeProjectWarnings (currentPtr , cfg .Runtime .ExperimentalFeatures .Projects .Projects )
774830 if len (conflicts ) > 0 {
775- project . Status . Warnings = append (project . Status .Warnings , conflicts ... )
831+ status . Warnings = append (status .Warnings , conflicts ... )
776832 }
777833}
778834
0 commit comments