pkg/bundle/patch/executor.go (466 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package patch import ( "context" "encoding/json" "errors" "fmt" "os" "regexp" "github.com/imdario/mergo" "github.com/jmespath/go-jmespath" bundlev1 "github.com/elastic/harp/api/gen/go/harp/bundle/v1" "github.com/elastic/harp/pkg/bundle/secret" "github.com/elastic/harp/pkg/bundle/selector" "github.com/elastic/harp/pkg/template/engine" ) type ruleAction uint const ( packageUnchanged ruleAction = iota packageUpdated packagedRemoved ) // ----------------------------------------------------------------------------- func executeRule(r *bundlev1.PatchRule, p *bundlev1.Package, values map[string]interface{}) (ruleAction, error) { // Check parameters if r == nil { return packageUnchanged, fmt.Errorf("cannot process nil rule") } if r.Package == nil { return packageUnchanged, fmt.Errorf("cannot process rule with nil package") } if p == nil { return packageUnchanged, fmt.Errorf("cannot process nil package") } // Compile selector s, err := compileSelector(r.Selector, values) if err != nil { return packageUnchanged, fmt.Errorf("unable to compile selector: %w", err) } // Package match selector specification if s.IsSatisfiedBy(p) { // Check removal request if r.Package.Remove { return packagedRemoved, nil } // Apply patch if err := applyPackagePatch(p, r.Package, values); err != nil { return packageUnchanged, fmt.Errorf("unable to apply patch to package `%s`: %w", p.Name, err) } // No error return packageUpdated, nil } // No error return packageUnchanged, nil } //nolint:gocyclo,funlen // to refactor func compileSelector(s *bundlev1.PatchSelector, values map[string]interface{}) (selector.Specification, error) { // Check parameters if s == nil { return nil, fmt.Errorf("cannot process nil selector") } // Has matchPath selector if s.MatchPath != nil { switch { case s.MatchPath.Strict != "": // Evaluation with template engine first value, err := engine.Render(s.MatchPath.Strict, map[string]interface{}{ "Values": values, }) if err != nil { return nil, fmt.Errorf("unable to evaluate template before matchPath build: %w", err) } // Return specification return selector.MatchPathStrict(value), nil case s.MatchPath.Glob != "": // Evaluation with template engine first value, err := engine.Render(s.MatchPath.Glob, map[string]interface{}{ "Values": values, }) if err != nil { return nil, fmt.Errorf("unable to evaluate template before matchPath build: %w", err) } // Return specification return selector.MatchPathGlob(value) case s.MatchPath.Regex != "": // Evaluation with template engine first value, err := engine.Render(s.MatchPath.Regex, map[string]interface{}{ "Values": values, }) if err != nil { return nil, fmt.Errorf("unable to evaluate template before matchPath build: %w", err) } // Return specification return selector.MatchPathRegex(value) default: return nil, errors.New("no strict, glob or regexp defined for path matcher") } } // Has jmesPath selector if s.JmesPath != "" { // Compile query exp, err := jmespath.Compile(s.JmesPath) if err != nil { return nil, fmt.Errorf("unable to compile jmesPath expression `%s`: %w", s.JmesPath, err) } // Return specification return selector.MatchJMESPath(exp), nil } // Has matchSecret selector if s.MatchSecret != nil { switch { case s.MatchSecret.Strict != "": // Evaluation with template engine first value, err := engine.Render(s.MatchSecret.Strict, map[string]interface{}{ "Values": values, }) if err != nil { return nil, fmt.Errorf("unable to evaluate template before matchSecret build: %w", err) } // Return specification return selector.MatchSecretStrict(value), nil case s.MatchSecret.Glob != "": // Evaluation with template engine first value, err := engine.Render(s.MatchSecret.Glob, map[string]interface{}{ "Values": values, }) if err != nil { return nil, fmt.Errorf("unable to evaluate template before matchSecret build: %w", err) } // Return specification return selector.MatchSecretGlob(value), nil case s.MatchSecret.Regex != "": // Evaluation with template engine first value, err := engine.Render(s.MatchSecret.Regex, map[string]interface{}{ "Values": values, }) if err != nil { return nil, fmt.Errorf("unable to evaluate template before matchSecret build: %w", err) } // Compile regexp re, err := regexp.Compile(value) if err != nil { return nil, fmt.Errorf("unable to compile matchSecret regexp `%s`: %w", s.MatchPath.Regex, err) } // Return specification return selector.MatchSecretRegex(re), nil default: return nil, errors.New("no strict, glob or regexp defined for secret matcher") } } if s.RegoFile != "" { // Read policy file policyFile, err := os.ReadFile(s.RegoFile) if err != nil { return nil, fmt.Errorf("unable to open rego policy file: %w", err) } // Build the specification return selector.MatchRego(context.Background(), string(policyFile)) } // Has rego policy if s.Rego != "" { // Return specification return selector.MatchRego(context.Background(), s.Rego) } // Has CEL expressions if len(s.Cel) > 0 { // Return specification return selector.MatchCEL(s.Cel) } // Fallback to default as error return nil, fmt.Errorf("no supported selector specified") } func applyPackagePatch(pkg *bundlev1.Package, p *bundlev1.PatchPackage, values map[string]interface{}) error { // Check parameters if pkg == nil { return fmt.Errorf("cannot process nil package") } if p == nil { return fmt.Errorf("cannot process nil patch") } // Patch concerns path if p.Path != nil { newPath, err := applyPackagePathPatch(pkg.Name, p.Path, values) if err != nil { return fmt.Errorf("unable to process `%s` name operations: %w", pkg.Name, err) } // Update package name pkg.Name = newPath } // Patch concerns annotations if p.Annotations != nil { if pkg.Annotations == nil { pkg.Annotations = map[string]string{} } if err := applyMapOperations(pkg.Annotations, p.Annotations, values); err != nil { return fmt.Errorf("unable to process `%s` annotations: %w", pkg.Name, err) } } // Patch concerns labels if p.Labels != nil { if pkg.Labels == nil { pkg.Labels = map[string]string{} } if err := applyMapOperations(pkg.Labels, p.Labels, values); err != nil { return fmt.Errorf("unable to process `%s` labels: %w", pkg.Name, err) } } // Patch concerns data if p.Data != nil { if pkg.Secrets == nil { pkg.Secrets = &bundlev1.SecretChain{} } if err := applySecretPatch(pkg.Secrets, p.Data, values); err != nil { return fmt.Errorf("unable to apply patch to secret data for package `%s`: %w", pkg.Name, err) } } return nil } //nolint:gocyclo // to refactor func applySecretPatch(secrets *bundlev1.SecretChain, op *bundlev1.PatchSecret, values map[string]interface{}) error { // Check parameters if secrets == nil { return fmt.Errorf("cannot process nil secrets") } if op == nil { return fmt.Errorf("cannot process nil patch") } // Patch concerns annotations if op.Annotations != nil { if secrets.Annotations == nil { secrets.Annotations = map[string]string{} } if err := applyMapOperations(secrets.Annotations, op.Annotations, values); err != nil { return fmt.Errorf("unable to process annotations: %w", err) } } // Patch concerns labels if op.Labels != nil { if secrets.Labels == nil { secrets.Labels = map[string]string{} } if err := applyMapOperations(secrets.Labels, op.Labels, values); err != nil { return fmt.Errorf("unable to process labels: %w", err) } } // Check template if op.Template != "" { // Compile template rendered, err := engine.Render(op.Template, map[string]interface{}{ "Values": values, }) if err != nil { return fmt.Errorf("unable to compile secret template: %w", err) } // Unmarshall as kv var kv map[string]string if errJSON := json.Unmarshal([]byte(rendered), &kv); errJSON != nil { return fmt.Errorf("unable to valudate rendered secret template as a valid JSON: %w", errJSON) } // Update secret data if secrets.Data == nil { secrets.Data = make([]*bundlev1.KV, 0) } updatedData, err := updateSecret(secrets.Data, kv) if err != nil { return fmt.Errorf("unable to uppdate kv from template: %w", err) } // Update secret data secrets.Data = updatedData } // Check K/V if op.Kv != nil { if secrets.Data == nil { secrets.Data = make([]*bundlev1.KV, 0) } updatedData, err := applySecretKVPatch(secrets.Data, op.Kv, values) if err != nil { return fmt.Errorf("unable to process kv: %w", err) } // Update secret data secrets.Data = updatedData } // No error return nil } //nolint:gocyclo // to refactor func applySecretKVPatch(kv []*bundlev1.KV, op *bundlev1.PatchOperation, values map[string]interface{}) ([]*bundlev1.KV, error) { // Check parameters if kv == nil { return nil, fmt.Errorf("cannot process nil kv list") } if op == nil { return nil, fmt.Errorf("cannot process nil operation") } var out []*bundlev1.KV // Remove all keys if len(op.RemoveKeys) > 0 { for _, rx := range op.RemoveKeys { re, err := regexp.Compile(rx) if err != nil { return nil, fmt.Errorf("unable to compile regexp for key deletion '%s': %w", rx, err) } // Add to remove if match one expression for _, k := range kv { if k == nil { continue } if re.MatchString(k.Key) { if op.Remove == nil { op.Remove = make([]string, 0) } op.Remove = append(op.Remove, k.Key) } } } } // Remove secret if len(op.Remove) > 0 { // Overwrite secret list out = removeSecret(kv, op.Remove) } // Add if op.Add != nil { inMap, err := precompileMap(op.Add, values) if err != nil { return nil, fmt.Errorf("unable to compile add map templates: %w", err) } if out, err = addSecret(kv, inMap); err != nil { return nil, fmt.Errorf("unable to add secret: %w", err) } } // Update if op.Update != nil { inMap, err := precompileMap(op.Update, values) if err != nil { return nil, fmt.Errorf("unable to compile update map templates: %w", err) } if out, err = updateSecret(kv, inMap); err != nil { return nil, fmt.Errorf("unable to update secret: %w", err) } } // Replace secrets if op.ReplaceKeys != nil { inMap, err := precompileMap(op.ReplaceKeys, values) if err != nil { return nil, fmt.Errorf("unable to compile replaceKeys map templates: %w", err) } // Replace keys out = replaceSecret(kv, inMap) } // No error return out, nil } func replaceSecret(input []*bundlev1.KV, replaceMap map[string]string) []*bundlev1.KV { for i, s := range input { // Ignore nil if s == nil { continue } // Check if the key should be replaced if newKey, ok := replaceMap[s.Key]; ok { s.Key = newKey } // Update the reference input[i] = s } // return input } func removeSecret(input []*bundlev1.KV, removeList []string) []*bundlev1.KV { out := []*bundlev1.KV{} for _, s := range input { // Ignore nil if s == nil { continue } found := false for _, toRemove := range removeList { if s.Key == toRemove { found = true } } // If not in list if !found { out = append(out, s) } } return out } func addSecret(input []*bundlev1.KV, newSecrets map[string]string) ([]*bundlev1.KV, error) { // Secret to add keys := []string{} out := []*bundlev1.KV{} // Check overrides for k := range newSecrets { found := false for _, s := range input { // Ignore nil if s == nil { continue } if s.Key == k { found = true } } // If not found if !found { keys = append(keys, k) } } // Add all existing secrets out = append(out, input...) // Add non-override key as new secret only for _, k := range keys { payload, err := secret.Pack(newSecrets[k]) if err != nil { return nil, fmt.Errorf("unable to pack secret: %w", err) } out = append(out, &bundlev1.KV{ Key: k, Type: fmt.Sprintf("%T", newSecrets[k]), Value: payload, }) } // No error return out, nil } func updateSecret(input []*bundlev1.KV, newSecrets map[string]string) ([]*bundlev1.KV, error) { // Secret to add out := []*bundlev1.KV{} for _, s := range input { // Ignore nil if s == nil { continue } // Check if concerned by updates v, ok := newSecrets[s.Key] if !ok { // Append to result out = append(out, s) // Skip modification continue } // Update with new value payload, err := secret.Pack(v) if err != nil { return nil, fmt.Errorf("unable to pack secret: %w", err) } // Append to result out = append(out, &bundlev1.KV{ Key: s.Key, Type: fmt.Sprintf("%T", v), Value: payload, }) } // No error return out, nil } //nolint:gocyclo // to refactor func applyMapOperations(input map[string]string, op *bundlev1.PatchOperation, values map[string]interface{}) error { // Check parameters if input == nil { return fmt.Errorf("cannot process nil map") } if op == nil { return fmt.Errorf("cannot process nil operation") } // Process all operations // Remove all keys if len(op.RemoveKeys) > 0 { for _, rx := range op.RemoveKeys { re, err := regexp.Compile(rx) if err != nil { return fmt.Errorf("unable to compile regexp for key deletion '%s': %w", rx, err) } // Add to remove if match one expression for k := range input { if re.MatchString(k) { if op.Remove == nil { op.Remove = make([]string, 0) } op.Remove = append(op.Remove, k) } } } } if len(op.Remove) > 0 { for _, toRemove := range op.Remove { delete(input, toRemove) } } if op.Add != nil { inMap, err := precompileMap(op.Add, values) if err != nil { return fmt.Errorf("unable to compile add map templates: %w", err) } if err := mergo.Merge(&input, inMap); err != nil { return fmt.Errorf("unable to add attributes to object: %w", err) } } if op.Update != nil { inMap, err := precompileMap(op.Update, values) if err != nil { return fmt.Errorf("unable to compile update map templates: %w", err) } if err := mergo.Merge(&input, inMap, mergo.WithOverride); err != nil { return fmt.Errorf("unable to add attributes to object: %w", err) } } if op.ReplaceKeys != nil { inMap, err := precompileMap(op.ReplaceKeys, values) if err != nil { return fmt.Errorf("unable to compile replaceKeys map templates: %w", err) } for oldKey, newKey := range inMap { if v, ok := input[oldKey]; ok { input[newKey] = v delete(input, oldKey) } } } return nil } func precompileMap(input map[string]string, values map[string]interface{}) (map[string]string, error) { output := map[string]string{} for k, v := range input { // Compile key key, err := engine.Render(k, map[string]interface{}{ "Values": values, }) if err != nil { return nil, fmt.Errorf("unable to compile key template `%s`: %w", k, err) } // Compile value val, err := engine.Render(v, map[string]interface{}{ "Values": values, }) if err != nil { return nil, fmt.Errorf("unable to compile value template `%s`: %w", v, err) } // Assign to result if _, ok := output[key]; !ok { output[key] = val } } // No error return output, nil } func applyPackagePathPatch(path string, op *bundlev1.PatchPackagePath, values map[string]interface{}) (string, error) { // Check parameters if op == nil { return "", fmt.Errorf("cannot process nil operation") } // Apply template transformation out, err := engine.Render(op.Template, map[string]interface{}{ "Values": values, "Path": path, }) if err != nil { return "", fmt.Errorf("unable to execute package name template of `%s`: %w", path, err) } // No error return out, nil }