go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/mrn/mrn.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package mrn 5 6 import ( 7 "errors" 8 "net/url" 9 "path" 10 "regexp" 11 "strings" 12 ) 13 14 func ServiceID(serviceName string, baseDomain string) string { 15 res := strings.TrimSuffix(serviceName, baseDomain) 16 return res 17 } 18 19 func IsValid(mrn string) bool { 20 x, err := url.Parse(mrn) 21 if err != nil { 22 return false 23 } 24 25 return x.Scheme == "" && x.Fragment == "" && x.RawQuery == "" 26 } 27 28 func NewMRN(fullResourceName string) (*MRN, error) { 29 u, err := url.Parse(fullResourceName) 30 if err != nil { 31 return nil, err 32 } 33 34 path := strings.TrimLeft(u.EscapedPath(), "/") 35 36 return &MRN{ 37 ServiceName: u.Host, 38 RelativeResourceName: path, 39 }, nil 40 } 41 42 func NewChildMRN(ownerMRN string, resource string, resourceID string) (*MRN, error) { 43 if !isValidResourceID(resourceID) { 44 return nil, errors.New("invalid " + resource + " ID: " + resourceID) 45 } 46 47 mrn, err := NewMRN(ownerMRN) 48 if err != nil { 49 return nil, err 50 } 51 52 mrn.RelativeResourceName = path.Join(mrn.RelativeResourceName, resource, resourceID) 53 return mrn, nil 54 } 55 56 func GetResource(mrn string, resource string) (string, error) { 57 parsed, err := NewMRN(mrn) 58 if err != nil { 59 return "", err 60 } 61 62 res, err := parsed.ResourceID(resource) 63 if err != nil { 64 return "", errors.New("invalid " + resource + " in mrn: " + mrn) 65 } 66 return res, nil 67 } 68 69 // SafeComponentString sanitizes a string so that it can be safely used as a uri component for mrns 70 func SafeComponentString(s string) string { 71 if s == "" { 72 return s 73 } 74 s = strings.ReplaceAll(s, " ", "_") 75 s = strings.ReplaceAll(s, "/", "-") 76 return s 77 } 78 79 // ID: lowercase letters, digits, dots or hyphens, fewer than 200 chars, more than 4 chars. 80 // They may include dots/hyphens etc, e.g. 1.1.2-tmp-configured 81 var reResourceID = regexp.MustCompile(`^([\d-_\.]|[a-zA-Z]){4,200}$`) 82 83 func isValidResourceID(id string) bool { 84 return reResourceID.MatchString(id) 85 } 86 87 // MRN follows Google's Design for resource names 88 // see https://cloud.google.com/apis/design/resource_names 89 type MRN struct { 90 ServiceName string 91 RelativeResourceName string 92 } 93 94 func (mrn *MRN) String() string { 95 return "//" + mrn.ServiceName + "/" + mrn.RelativeResourceName 96 } 97 98 func (mrn *MRN) Basename() string { 99 keyValues := strings.Split(mrn.RelativeResourceName, "/") 100 if len(keyValues) == 0 { 101 return "" 102 } 103 return keyValues[len(keyValues)-1] 104 } 105 106 func (mrn *MRN) ResourceID(collectionId string) (string, error) { 107 keyValues := strings.Split(mrn.RelativeResourceName, "/") 108 for i := 0; i < len(keyValues); { 109 if keyValues[i] == collectionId { 110 if i+1 < len(keyValues) { 111 return keyValues[i+1], nil 112 } else { 113 return "", errors.New("invalid mrn collection id scheme: " + mrn.String()) 114 } 115 } 116 i = i + 2 117 } 118 return "", errors.New("could not find collection id: " + collectionId) 119 } 120 121 func (mrn *MRN) Equals(resource string) bool { 122 parsed, err := NewMRN(resource) 123 if err != nil { 124 return false 125 } 126 if parsed.ServiceName == mrn.ServiceName && parsed.RelativeResourceName == mrn.RelativeResourceName { 127 return true 128 } 129 return false 130 }