github.com/dhaiducek/policy-generator-plugin@v1.99.99/internal/patches.go (about) 1 // Copyright Contributors to the Open Cluster Management project 2 package internal 3 4 import ( 5 "errors" 6 "fmt" 7 "path" 8 9 yaml "gopkg.in/yaml.v3" 10 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 11 "sigs.k8s.io/kustomize/api/krusty" 12 "sigs.k8s.io/kustomize/kyaml/filesys" 13 ) 14 15 type manifestPatcher struct { 16 // The manifests to patch. 17 manifests []map[string]interface{} 18 // The Kustomize patches to apply on the manifests. Note that modifications are made 19 // to the input maps. If this is an issue, provide a deep copy of the patches. 20 patches []map[string]interface{} 21 } 22 23 // validateManifestInfo verifies that the apiVersion, kind, metadata.name fields from a manifest 24 // are set. If at least one is not present, an error is returned based on the input error template 25 // which accepts the field name. 26 func validateManifestInfo(manifest map[string]interface{}, errTemplate string) error { 27 apiVersion, _, _ := unstructured.NestedString(manifest, "apiVersion") 28 if apiVersion == "" { 29 return fmt.Errorf(errTemplate, "apiVersion") 30 } 31 32 kind, _, _ := unstructured.NestedString(manifest, "kind") 33 if kind == "" { 34 return fmt.Errorf(errTemplate, "kind") 35 } 36 37 name, _, _ := unstructured.NestedString(manifest, "metadata", "name") 38 if name == "" { 39 return fmt.Errorf(errTemplate, "metadata.name") 40 } 41 42 return nil 43 } 44 45 // Validate performs basic validatation of the manifests and patches. Any missing values in the 46 // patches that can be derived are added to the patches. An error is returned if a manifest or 47 // patch is invalid. 48 func (m *manifestPatcher) Validate() error { 49 if len(m.manifests) == 0 { 50 return errors.New("there must be one or more manifests") 51 } 52 53 // Validate the manifest fields for applying patches 54 const errTemplate = `all manifests must have the "%s" field set to a non-empty string` 55 for _, manifest := range m.manifests { 56 err := validateManifestInfo(manifest, errTemplate) 57 if err != nil { 58 return err 59 } 60 } 61 62 // If there is more than a single manifest, the patch must contain a name, kind, and apiVersion. 63 if len(m.manifests) > 1 { 64 const patchErrTemplate = `patches must have the "%s" field set to a non-empty string ` + 65 `when there is more than one manifest it can apply to` 66 67 for _, patch := range m.patches { 68 err := validateManifestInfo(patch, patchErrTemplate) 69 if err != nil { 70 return err 71 } 72 } 73 74 // At this point, there is a reasonable chance that the patch is valid. Kustomize can handle 75 // further validation. 76 return nil 77 } 78 79 // At this point, we know we are only dealing with a single manifest, so we can assume all 80 // the patches are meant to apply to it. 81 manifest := (m.manifests)[0] 82 // The following fields have already been confirmed to exist and be valid previously in this 83 // method. 84 apiVersion, _, _ := unstructured.NestedString(manifest, "apiVersion") 85 kind, _, _ := unstructured.NestedString(manifest, "kind") 86 name, _, _ := unstructured.NestedString(manifest, "metadata", "name") 87 // The namespace would only apply to a manifest for a namespaced resource, so this is optional. 88 // Treat an empty string as meaning the manifest is for a cluster-wide resource. 89 namespace, _, _ := unstructured.NestedString(manifest, "metadata", "namespace") 90 91 // Apply defaults on the patches 92 for i := range m.patches { 93 err := setPatchDefaults(apiVersion, kind, name, namespace, m.patches[i]) 94 if err != nil { 95 return err 96 } 97 } 98 99 return nil 100 } 101 102 // setPatchDefaults is a helper function for Validate that sets any missing values on the patches 103 // that can be derived. An error is returned if a patch is in an invalid format. 104 func setPatchDefaults( 105 apiVersion, kind, name, namespace string, patch map[string]interface{}, 106 ) error { 107 errTemplate := `failed to retrieve the "%s" field from the manifest of name "` + name + 108 `"` + ` and kind "` + kind + `": %v` 109 setErrTemplate := `failed to set the "%s" field on the patch from the manifest of name "` + 110 name + `"` + ` and kind "` + kind + `": %v` 111 112 patchAPIVersion, _, err := unstructured.NestedString(patch, "apiVersion") 113 if err != nil { 114 return fmt.Errorf(errTemplate, "apiVersion", err) 115 } 116 117 if patchAPIVersion == "" { 118 err = unstructured.SetNestedField(patch, apiVersion, "apiVersion") 119 if err != nil { 120 return fmt.Errorf(setErrTemplate, "apiVersion", err) 121 } 122 } 123 124 patchKind, _, err := unstructured.NestedString(patch, "kind") 125 if err != nil { 126 return fmt.Errorf(errTemplate, "kind", err) 127 } 128 129 if patchKind == "" { 130 err = unstructured.SetNestedField(patch, kind, "kind") 131 if err != nil { 132 return fmt.Errorf(setErrTemplate, "kind", err) 133 } 134 } 135 136 patchName, _, err := unstructured.NestedString(patch, "metadata", "name") 137 if err != nil { 138 return fmt.Errorf(errTemplate, "metadata.name", err) 139 } 140 141 if patchName == "" { 142 err = unstructured.SetNestedField(patch, name, "metadata", "name") 143 if err != nil { 144 return fmt.Errorf(setErrTemplate, "metadata.name", err) 145 } 146 } 147 148 patchNamespace, _, err := unstructured.NestedString(patch, "metadata", "namespace") 149 if err != nil { 150 return fmt.Errorf(errTemplate, "metadata.namespace", err) 151 } 152 153 if patchNamespace == "" { 154 err = unstructured.SetNestedField(patch, namespace, "metadata", "namespace") 155 if err != nil { 156 return fmt.Errorf(setErrTemplate, "metadata.namespace", err) 157 } 158 } 159 160 return nil 161 } 162 163 // ApplyPatches applies the Kustomize patches on the input manifests using Kustomize and returns 164 // the patched manifests. An error is returned if the patches can't be applied. This should be 165 // run after the Validate method. 166 func (m *manifestPatcher) ApplyPatches() ([]map[string]interface{}, error) { 167 kustomizeDir := "kustomize" 168 169 // Create the file system in memory with the Kustomize YAML files 170 fSys := filesys.MakeFsInMemory() 171 172 err := fSys.Mkdir(kustomizeDir) 173 if err != nil { 174 return nil, fmt.Errorf("an unexpected error occurred when configuring Kustomize: %w", err) 175 } 176 177 kustomizationYAMLFile := map[string][]interface{}{ 178 "resources": {}, 179 "patchesStrategicMerge": {}, 180 } 181 182 options := []struct { 183 optionType string 184 kustomizeKey string 185 objects []map[string]interface{} 186 }{ 187 {"manifest", "resources", m.manifests}, 188 {"patch", "patchesStrategicMerge", m.patches}, 189 } 190 for _, option := range options { 191 for i, object := range option.objects { 192 var objectYAML []byte 193 objectYAML, err := yaml.Marshal(object) 194 const errTemplate = "an unexpected error occurred when converting the %s back to " + 195 "YAML: %w" 196 197 if err != nil { 198 return nil, fmt.Errorf(errTemplate, option.optionType, err) 199 } 200 201 manifestFileName := fmt.Sprintf("%s%d.yaml", option.optionType, i) 202 203 err = fSys.WriteFile(path.Join(kustomizeDir, manifestFileName), objectYAML) 204 if err != nil { 205 return nil, fmt.Errorf(errTemplate, option.optionType, err) 206 } 207 208 kustomizationYAMLFile[option.kustomizeKey] = append( 209 kustomizationYAMLFile[option.kustomizeKey], manifestFileName, 210 ) 211 } 212 } 213 214 var kustomizationYAML []byte 215 kustomizationYAML, err = yaml.Marshal(kustomizationYAMLFile) 216 const errTemplate = "an unexpected error occurred when creating the kustomization.yaml file: %w" 217 218 if err != nil { 219 return nil, fmt.Errorf(errTemplate, err) 220 } 221 222 err = fSys.WriteFile(path.Join(kustomizeDir, "kustomization.yaml"), kustomizationYAML) 223 if err != nil { 224 return nil, fmt.Errorf(errTemplate, err) 225 } 226 227 k := krusty.MakeKustomizer(krusty.MakeDefaultOptions()) 228 229 resMap, err := k.Run(fSys, "kustomize") 230 if err != nil { 231 err = fmt.Errorf( 232 "failed to apply the patch(es) to the manifest(s) using Kustomize: %w", err, 233 ) 234 235 return nil, err 236 } 237 238 manifestsYAML, err := resMap.AsYaml() 239 if err != nil { 240 return nil, fmt.Errorf("failed to convert the patched manifest(s) back to YAML: %w", err) 241 } 242 243 manifests, err := unmarshalManifestBytes(manifestsYAML) 244 if err != nil { 245 return nil, fmt.Errorf("failed to read the patched manifest(s): %w", err) 246 } 247 248 return manifests, nil 249 }