@@ -329,7 +329,7 @@ func createValidatingWebhook(ctx context.Context, cfg *WebhookConfig, caCert []b
329329 Resources : []string {porchapi .PackageRevisionGVR .Resource },
330330 },
331331 }},
332- AdmissionReviewVersions : []string {"v1" , "v1beta1" },
332+ AdmissionReviewVersions : []string {"v1" },
333333 SideEffects : & none ,
334334 FailurePolicy : & fail ,
335335 TimeoutSeconds : & cfg .timeout ,
@@ -357,7 +357,7 @@ func createValidatingWebhook(ctx context.Context, cfg *WebhookConfig, caCert []b
357357 Resources : []string {"repositories" },
358358 },
359359 }},
360- AdmissionReviewVersions : []string {"v1" , "v1beta1" },
360+ AdmissionReviewVersions : []string {"v1" },
361361 SideEffects : & none ,
362362 FailurePolicy : & fail ,
363363 TimeoutSeconds : & cfg .timeout ,
@@ -661,12 +661,28 @@ func validateRepository(w http.ResponseWriter, r *http.Request, porchClient clie
661661
662662 var attempted configapi.Repository
663663 if err := json .Unmarshal (admissionReviewRequest .Request .Object .Raw , & attempted ); err != nil {
664+ klog .Errorf ("failed to unmarshal repository object: %v" , err )
664665 writeErr (fmt .Sprintf ("could not unmarshal repository: %v" , err ), & w )
665666 return
666667 }
667668
669+ // For UPDATE operations, check if URL or directory is being modified
670+ if admissionReviewRequest .Request .Operation == admissionv1 .Update {
671+ var existing configapi.Repository
672+ if err := json .Unmarshal (admissionReviewRequest .Request .OldObject .Raw , & existing ); err != nil {
673+ klog .Errorf ("failed to unmarshal existing repository object: %v" , err )
674+ writeErr (fmt .Sprintf ("could not unmarshal existing repository: %v" , err ), & w )
675+ return
676+ }
677+
678+ if err := validateRepositoryModification (& existing , & attempted , admissionReviewRequest , & w ); err != nil {
679+ return
680+ }
681+ }
682+
668683 var repoList configapi.RepositoryList
669684 if err := porchClient .List (context .Background (), & repoList ); err != nil {
685+ klog .Errorf ("failed to list repositories: %v" , err )
670686 writeErr (fmt .Sprintf ("could not list repositories: %v" , err ), & w )
671687 return
672688 }
@@ -676,22 +692,8 @@ func validateRepository(w http.ResponseWriter, r *http.Request, porchClient clie
676692 continue
677693 }
678694 if isConflict (& existing , & attempted ) {
679- resp := & admissionv1.AdmissionResponse {
680- Allowed : false ,
681- Result : & metav1.Status {
682- Status : "Failure" ,
683- Message : fmt .Sprintf ("Repository conflict with existing repository: %s/%s" , existing .Namespace , existing .Name ),
684- Reason : "RepositoryConflict" ,
685- },
686- }
687- responseBytes , _ := constructResponse (resp , admissionReviewRequest )
688- w .Header ().Set ("Content-Type" , "application/json" )
689- _ , err = w .Write (responseBytes )
690- if err != nil {
691- errMsg := fmt .Sprintf ("error writing response: %v" , err )
692- writeErr (errMsg , & w )
693- return
694- }
695+ klog .Errorf ("repository validation failed: conflict detected between attempted %s/%s and existing %s/%s" , attempted .Namespace , attempted .Name , existing .Namespace , existing .Name )
696+ writeModificationResponse (fmt .Sprintf ("Repository conflict with existing repository: %s/%s" , existing .Namespace , existing .Name ), "RepositoryConflict" , admissionReviewRequest , & w )
695697 return
696698 }
697699 }
@@ -766,18 +768,64 @@ func isConflict(existing, attempted *configapi.Repository) bool {
766768}
767769
768770func isNestedConflict (a , b string ) bool {
769- aParts := strings .Split (a , "/" )
770- bParts := strings .Split (b , "/" )
771+ // Check if one path is nested within the other using filepath.Rel
772+ relAtoB , err1 := filepath .Rel (a , b )
773+ relBtoA , err2 := filepath .Rel (b , a )
771774
772- // a is base of b
773- if len ( aParts ) < len ( bParts ) && strings .Join ( bParts [: len ( aParts )] , "/" ) == a {
774- return true
775+ // If either relative path doesn't start with "../", it means one is nested in the other
776+ if err1 == nil && ! strings .HasPrefix ( relAtoB , ".. /" ) && relAtoB != "." {
777+ return true // b is nested within a
775778 }
776-
777- // b is base of a
778- if len (bParts ) < len (aParts ) && strings .Join (aParts [:len (bParts )], "/" ) == b {
779- return true
779+ if err2 == nil && ! strings .HasPrefix (relBtoA , "../" ) && relBtoA != "." {
780+ return true // a is nested within b
780781 }
781782
782783 return false
783784}
785+
786+ func validateRepositoryModification (existing , attempted * configapi.Repository , admissionReviewRequest * admissionv1.AdmissionReview , w * http.ResponseWriter ) error {
787+ if isURLModified (existing , attempted ) {
788+ klog .Errorf ("repository validation failed: URL modification not allowed for %s/%s - delete the existing repository and create it if you want to change the URL" , attempted .Namespace , attempted .Name )
789+ writeModificationResponse ("Repository URL cannot be modified after creation. Please delete the existing repository and create it if you want to change the URL" , "URLModificationNotAllowed" , admissionReviewRequest , w )
790+ return fmt .Errorf ("URL modification not allowed" )
791+ }
792+
793+ if isDirectoryModified (existing , attempted ) {
794+ klog .Errorf ("repository validation failed: directory modification not allowed for %s/%s - delete the existing repository and create it if you want to change the directory" , attempted .Namespace , attempted .Name )
795+ writeModificationResponse ("Repository directory cannot be modified after creation. Please delete the existing repository and create it if you want to change the directory" , "DirectoryModificationNotAllowed" , admissionReviewRequest , w )
796+ return fmt .Errorf ("directory modification not allowed" )
797+ }
798+
799+ return nil
800+ }
801+
802+ func isURLModified (existing , attempted * configapi.Repository ) bool {
803+ return existing .Spec .Git .Repo != attempted .Spec .Git .Repo
804+ }
805+
806+ func isDirectoryModified (existing , attempted * configapi.Repository ) bool {
807+ return existing .Spec .Git .Directory != attempted .Spec .Git .Directory
808+ }
809+
810+ func writeModificationResponse (message , reason string , admissionReviewRequest * admissionv1.AdmissionReview , w * http.ResponseWriter ) {
811+ resp := & admissionv1.AdmissionResponse {
812+ Allowed : false ,
813+ Result : & metav1.Status {
814+ Status : "Failure" ,
815+ Message : message ,
816+ Reason : metav1 .StatusReason (reason ),
817+ },
818+ }
819+ responseBytes , err := constructResponse (resp , admissionReviewRequest )
820+ if err != nil {
821+ klog .Errorf ("failed to construct modification response: %v" , err )
822+ writeErr (fmt .Sprintf ("error constructing response: %v" , err ), w )
823+ return
824+ }
825+ (* w ).Header ().Set ("Content-Type" , "application/json" )
826+ _ , err = (* w ).Write (responseBytes )
827+ if err != nil {
828+ errMsg := fmt .Sprintf ("error writing response: %v" , err )
829+ writeErr (errMsg , w )
830+ }
831+ }
0 commit comments