1/* 2Copyright 2016 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package set 18 19import ( 20 "strings" 21 22 "k8s.io/api/core/v1" 23 "k8s.io/apimachinery/pkg/runtime" 24 "k8s.io/apimachinery/pkg/util/sets" 25 "k8s.io/apimachinery/pkg/util/strategicpatch" 26 "k8s.io/cli-runtime/pkg/resource" 27) 28 29// selectContainers allows one or more containers to be matched against a string or wildcard 30func selectContainers(containers []v1.Container, spec string) ([]*v1.Container, []*v1.Container) { 31 out := []*v1.Container{} 32 skipped := []*v1.Container{} 33 for i, c := range containers { 34 if selectString(c.Name, spec) { 35 out = append(out, &containers[i]) 36 } else { 37 skipped = append(skipped, &containers[i]) 38 } 39 } 40 return out, skipped 41} 42 43// selectString returns true if the provided string matches spec, where spec is a string with 44// a non-greedy '*' wildcard operator. 45// TODO: turn into a regex and handle greedy matches and backtracking. 46func selectString(s, spec string) bool { 47 if spec == "*" { 48 return true 49 } 50 if !strings.Contains(spec, "*") { 51 return s == spec 52 } 53 54 pos := 0 55 match := true 56 parts := strings.Split(spec, "*") 57Loop: 58 for i, part := range parts { 59 if len(part) == 0 { 60 continue 61 } 62 next := strings.Index(s[pos:], part) 63 switch { 64 // next part not in string 65 case next < pos: 66 fallthrough 67 // first part does not match start of string 68 case i == 0 && pos != 0: 69 fallthrough 70 // last part does not exactly match remaining part of string 71 case i == (len(parts)-1) && len(s) != (len(part)+next): 72 match = false 73 break Loop 74 default: 75 pos = next 76 } 77 } 78 return match 79} 80 81// Patch represents the result of a mutation to an object. 82type Patch struct { 83 Info *resource.Info 84 Err error 85 86 Before []byte 87 After []byte 88 Patch []byte 89} 90 91// PatchFn is a function type that accepts an info object and returns a byte slice. 92// Implementations of PatchFn should update the object and return it encoded. 93type PatchFn func(runtime.Object) ([]byte, error) 94 95// CalculatePatch calls the mutation function on the provided info object, and generates a strategic merge patch for 96// the changes in the object. Encoder must be able to encode the info into the appropriate destination type. 97// This function returns whether the mutation function made any change in the original object. 98func CalculatePatch(patch *Patch, encoder runtime.Encoder, mutateFn PatchFn) bool { 99 patch.Before, patch.Err = runtime.Encode(encoder, patch.Info.Object) 100 patch.After, patch.Err = mutateFn(patch.Info.Object) 101 if patch.Err != nil { 102 return true 103 } 104 if patch.After == nil { 105 return false 106 } 107 108 patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, patch.Info.Object) 109 return true 110} 111 112// CalculatePatches calculates patches on each provided info object. If the provided mutateFn 113// makes no change in an object, the object is not included in the final list of patches. 114func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn PatchFn) []*Patch { 115 var patches []*Patch 116 for _, info := range infos { 117 patch := &Patch{Info: info} 118 if CalculatePatch(patch, encoder, mutateFn) { 119 patches = append(patches, patch) 120 } 121 } 122 return patches 123} 124 125func findEnv(env []v1.EnvVar, name string) (v1.EnvVar, bool) { 126 for _, e := range env { 127 if e.Name == name { 128 return e, true 129 } 130 } 131 return v1.EnvVar{}, false 132} 133 134func updateEnv(existing []v1.EnvVar, env []v1.EnvVar, remove []string) []v1.EnvVar { 135 out := []v1.EnvVar{} 136 covered := sets.NewString(remove...) 137 for _, e := range existing { 138 if covered.Has(e.Name) { 139 continue 140 } 141 newer, ok := findEnv(env, e.Name) 142 if ok { 143 covered.Insert(e.Name) 144 out = append(out, newer) 145 continue 146 } 147 out = append(out, e) 148 } 149 for _, e := range env { 150 if covered.Has(e.Name) { 151 continue 152 } 153 covered.Insert(e.Name) 154 out = append(out, e) 155 } 156 return out 157} 158