github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/resources/claim.go (about) 1 // Copyright 2021 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package resources 5 6 import ( 7 "strings" 8 9 "github.com/juju/errors" 10 "k8s.io/apimachinery/pkg/api/meta" 11 12 "github.com/juju/juju/caas/kubernetes/provider/constants" 13 ) 14 15 // Claim represents an assertion over a generic Kubernetes object to assert 16 // ownership. These are used in Juju for cluster scoped resources to assert that 17 // that Juju is not going to take ownership of an object that was not created by 18 // itself. 19 type Claim interface { 20 // Assert defines the assertion to run. Returns true if a claim is asserted 21 // over the provided object or if an error occurred where a claim can not be 22 // made. 23 Assert(obj interface{}) (bool, error) 24 } 25 26 // ClaimFn is a helper type for making Claim types out of functions. See Claim 27 type ClaimFn func(obj interface{}) (bool, error) 28 29 var ( 30 // ClaimJujuOwnership asserts that the Kubernetes object has labels that 31 // in line with Juju management "ownership". 32 ClaimJujuOwnership = ClaimAggregateOr( 33 ClaimFn(claimIsManagedByJuju), 34 ClaimFn(claimHasJujuLabel), 35 ) 36 ) 37 38 func (c ClaimFn) Assert(obj interface{}) (bool, error) { 39 return c(obj) 40 } 41 42 // ClaimAggregateOr runs multiple claims looking for the first true condition. 43 // If no claims are provided or no claim returns true false is returned. The 44 // first claim to error stops execution. 45 func ClaimAggregateOr(claims ...Claim) Claim { 46 return ClaimFn(func(obj interface{}) (bool, error) { 47 for _, claim := range claims { 48 if r, err := claim.Assert(obj); err != nil { 49 return r, err 50 } else if r { 51 return r, err 52 } 53 } 54 return false, nil 55 }) 56 } 57 58 // claimHasAJujuLabel is a throw everything against the wall and see what sticks 59 // assertion. It itterates all labels of the object trying to find a key that 60 // has the lowercase word "juju". We use this because our labeling at one stage 61 // is a bit hit and miss and no consitancy to fall back on. 62 // TODO: Remove in Juju 3.0 63 func claimHasJujuLabel(obj interface{}) (bool, error) { 64 if obj == nil { 65 return false, errors.NewNotValid(nil, "obj for claim cannot be nil") 66 } 67 68 metaObj, err := meta.Accessor(obj) 69 if err != nil { 70 return false, errors.Annotate(err, "asserting Kubernetes object has Juju labels") 71 } 72 for k := range metaObj.GetLabels() { 73 if strings.Contains(k, "juju") { 74 return true, nil 75 } 76 } 77 return false, nil 78 } 79 80 // claimIsManagedByJuju is a check to assert that the Kubernetes object provied 81 // is managed by Juju by having the label key and value of 82 // app.kubernetes.io/managed-by: juju. 83 func claimIsManagedByJuju(obj interface{}) (bool, error) { 84 if obj == nil { 85 return false, errors.NewNotValid(nil, "obj for claim cannot be nil") 86 } 87 88 metaObj, err := meta.Accessor(obj) 89 if err != nil { 90 return false, errors.Annotate(err, "asserting Kubernetes object has managed by juju label") 91 } 92 93 val, has := metaObj.GetLabels()[constants.LabelKubernetesAppManaged] 94 if !has { 95 return false, nil 96 } 97 return val == "juju", nil 98 } 99 100 // RunClaims runs the provided claims until the first true condition is found or 101 // the first error occurs. If no claims are provided then true is returned. 102 func RunClaims(claims ...Claim) Claim { 103 return ClaimFn(func(obj interface{}) (bool, error) { 104 for _, claim := range claims { 105 if r, err := claim.Assert(obj); err != nil { 106 return r, err 107 } else if r { 108 return r, err 109 } 110 } 111 return true, nil 112 }) 113 }