admission.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package podpreset
  14. import (
  15. "context"
  16. "fmt"
  17. "io"
  18. "reflect"
  19. "strings"
  20. "k8s.io/klog"
  21. settingsv1alpha1 "k8s.io/api/settings/v1alpha1"
  22. "k8s.io/apimachinery/pkg/api/errors"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. "k8s.io/apimachinery/pkg/labels"
  25. utilerrors "k8s.io/apimachinery/pkg/util/errors"
  26. "k8s.io/apimachinery/pkg/util/validation/field"
  27. "k8s.io/apiserver/pkg/admission"
  28. genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
  29. "k8s.io/client-go/informers"
  30. "k8s.io/client-go/kubernetes"
  31. settingsv1alpha1listers "k8s.io/client-go/listers/settings/v1alpha1"
  32. api "k8s.io/kubernetes/pkg/apis/core"
  33. "k8s.io/kubernetes/pkg/apis/core/pods"
  34. apiscorev1 "k8s.io/kubernetes/pkg/apis/core/v1"
  35. )
  36. const (
  37. annotationPrefix = "podpreset.admission.kubernetes.io"
  38. // PluginName is a string with the name of the plugin
  39. PluginName = "PodPreset"
  40. )
  41. // Register registers a plugin
  42. func Register(plugins *admission.Plugins) {
  43. plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
  44. return NewPlugin(), nil
  45. })
  46. }
  47. // Plugin is an implementation of admission.Interface.
  48. type Plugin struct {
  49. *admission.Handler
  50. client kubernetes.Interface
  51. lister settingsv1alpha1listers.PodPresetLister
  52. }
  53. var _ admission.MutationInterface = &Plugin{}
  54. var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&Plugin{})
  55. var _ = genericadmissioninitializer.WantsExternalKubeClientSet(&Plugin{})
  56. // NewPlugin creates a new pod preset admission plugin.
  57. func NewPlugin() *Plugin {
  58. return &Plugin{
  59. Handler: admission.NewHandler(admission.Create, admission.Update),
  60. }
  61. }
  62. // ValidateInitialization validates the Plugin was initialized properly
  63. func (p *Plugin) ValidateInitialization() error {
  64. if p.client == nil {
  65. return fmt.Errorf("%s requires a client", PluginName)
  66. }
  67. if p.lister == nil {
  68. return fmt.Errorf("%s requires a lister", PluginName)
  69. }
  70. return nil
  71. }
  72. // SetExternalKubeClientSet registers the client into Plugin
  73. func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
  74. p.client = client
  75. }
  76. // SetExternalKubeInformerFactory registers an informer factory into Plugin
  77. func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
  78. podPresetInformer := f.Settings().V1alpha1().PodPresets()
  79. p.lister = podPresetInformer.Lister()
  80. p.SetReadyFunc(podPresetInformer.Informer().HasSynced)
  81. }
  82. // Admit injects a pod with the specific fields for each pod preset it matches.
  83. func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
  84. // Ignore all calls to subresources or resources other than pods.
  85. // Ignore all operations other than CREATE.
  86. if len(a.GetSubresource()) != 0 || a.GetResource().GroupResource() != api.Resource("pods") || a.GetOperation() != admission.Create {
  87. return nil
  88. }
  89. pod, ok := a.GetObject().(*api.Pod)
  90. if !ok {
  91. return errors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
  92. }
  93. if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod {
  94. return nil
  95. }
  96. // Ignore if exclusion annotation is present
  97. if podAnnotations := pod.GetAnnotations(); podAnnotations != nil {
  98. klog.V(5).Infof("Looking at pod annotations, found: %v", podAnnotations)
  99. if podAnnotations[api.PodPresetOptOutAnnotationKey] == "true" {
  100. return nil
  101. }
  102. }
  103. list, err := p.lister.PodPresets(a.GetNamespace()).List(labels.Everything())
  104. if err != nil {
  105. return fmt.Errorf("listing pod presets failed: %v", err)
  106. }
  107. matchingPPs, err := filterPodPresets(list, pod)
  108. if err != nil {
  109. return fmt.Errorf("filtering pod presets failed: %v", err)
  110. }
  111. if len(matchingPPs) == 0 {
  112. return nil
  113. }
  114. presetNames := make([]string, len(matchingPPs))
  115. for i, pp := range matchingPPs {
  116. presetNames[i] = pp.GetName()
  117. }
  118. // detect merge conflict
  119. err = safeToApplyPodPresetsOnPod(pod, matchingPPs)
  120. if err != nil {
  121. // conflict, ignore the error, but raise an event
  122. klog.Warningf("conflict occurred while applying podpresets: %s on pod: %v err: %v",
  123. strings.Join(presetNames, ","), pod.GetGenerateName(), err)
  124. return nil
  125. }
  126. applyPodPresetsOnPod(pod, matchingPPs)
  127. klog.Infof("applied podpresets: %s successfully on Pod: %+v ", strings.Join(presetNames, ","), pod.GetGenerateName())
  128. return nil
  129. }
  130. // filterPodPresets returns list of PodPresets which match given Pod.
  131. func filterPodPresets(list []*settingsv1alpha1.PodPreset, pod *api.Pod) ([]*settingsv1alpha1.PodPreset, error) {
  132. var matchingPPs []*settingsv1alpha1.PodPreset
  133. for _, pp := range list {
  134. selector, err := metav1.LabelSelectorAsSelector(&pp.Spec.Selector)
  135. if err != nil {
  136. return nil, fmt.Errorf("label selector conversion failed: %v for selector: %v", pp.Spec.Selector, err)
  137. }
  138. // check if the pod labels match the selector
  139. if !selector.Matches(labels.Set(pod.Labels)) {
  140. continue
  141. }
  142. klog.V(4).Infof("PodPreset %s matches pod %s labels", pp.GetName(), pod.GetName())
  143. matchingPPs = append(matchingPPs, pp)
  144. }
  145. return matchingPPs, nil
  146. }
  147. // safeToApplyPodPresetsOnPod determines if there is any conflict in information
  148. // injected by given PodPresets in the Pod.
  149. func safeToApplyPodPresetsOnPod(pod *api.Pod, podPresets []*settingsv1alpha1.PodPreset) error {
  150. var errs []error
  151. // volumes attribute is defined at the Pod level, so determine if volumes
  152. // injection is causing any conflict.
  153. if _, err := mergeVolumes(pod.Spec.Volumes, podPresets); err != nil {
  154. errs = append(errs, err)
  155. }
  156. pods.VisitContainersWithPath(&pod.Spec, func(c *api.Container, _ *field.Path) bool {
  157. if err := safeToApplyPodPresetsOnContainer(c, podPresets); err != nil {
  158. errs = append(errs, err)
  159. }
  160. return true
  161. })
  162. return utilerrors.NewAggregate(errs)
  163. }
  164. // safeToApplyPodPresetsOnContainer determines if there is any conflict in
  165. // information injected by given PodPresets in the given container.
  166. func safeToApplyPodPresetsOnContainer(ctr *api.Container, podPresets []*settingsv1alpha1.PodPreset) error {
  167. var errs []error
  168. // check if it is safe to merge env vars and volume mounts from given podpresets and
  169. // container's existing env vars.
  170. if _, err := mergeEnv(ctr.Env, podPresets); err != nil {
  171. errs = append(errs, err)
  172. }
  173. if _, err := mergeVolumeMounts(ctr.VolumeMounts, podPresets); err != nil {
  174. errs = append(errs, err)
  175. }
  176. return utilerrors.NewAggregate(errs)
  177. }
  178. // mergeEnv merges a list of env vars with the env vars injected by given list podPresets.
  179. // It returns an error if it detects any conflict during the merge.
  180. func mergeEnv(envVars []api.EnvVar, podPresets []*settingsv1alpha1.PodPreset) ([]api.EnvVar, error) {
  181. origEnv := map[string]api.EnvVar{}
  182. for _, v := range envVars {
  183. origEnv[v.Name] = v
  184. }
  185. mergedEnv := make([]api.EnvVar, len(envVars))
  186. copy(mergedEnv, envVars)
  187. var errs []error
  188. for _, pp := range podPresets {
  189. for _, v := range pp.Spec.Env {
  190. internalEnv := api.EnvVar{}
  191. if err := apiscorev1.Convert_v1_EnvVar_To_core_EnvVar(&v, &internalEnv, nil); err != nil {
  192. return nil, err
  193. }
  194. found, ok := origEnv[v.Name]
  195. if !ok {
  196. // if we don't already have it append it and continue
  197. origEnv[v.Name] = internalEnv
  198. mergedEnv = append(mergedEnv, internalEnv)
  199. continue
  200. }
  201. // make sure they are identical or throw an error
  202. if !reflect.DeepEqual(found, internalEnv) {
  203. errs = append(errs, fmt.Errorf("merging env for %s has a conflict on %s: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), v.Name, v, found))
  204. }
  205. }
  206. }
  207. err := utilerrors.NewAggregate(errs)
  208. if err != nil {
  209. return nil, err
  210. }
  211. return mergedEnv, err
  212. }
  213. type envFromMergeKey struct {
  214. prefix string
  215. configMapRefName string
  216. secretRefName string
  217. }
  218. func newEnvFromMergeKey(e api.EnvFromSource) envFromMergeKey {
  219. k := envFromMergeKey{prefix: e.Prefix}
  220. if e.ConfigMapRef != nil {
  221. k.configMapRefName = e.ConfigMapRef.Name
  222. }
  223. if e.SecretRef != nil {
  224. k.secretRefName = e.SecretRef.Name
  225. }
  226. return k
  227. }
  228. func mergeEnvFrom(envSources []api.EnvFromSource, podPresets []*settingsv1alpha1.PodPreset) ([]api.EnvFromSource, error) {
  229. var mergedEnvFrom []api.EnvFromSource
  230. // merge envFrom using a identify key to ensure Admit reinvocations are idempotent
  231. origEnvSources := map[envFromMergeKey]api.EnvFromSource{}
  232. for _, envSource := range envSources {
  233. origEnvSources[newEnvFromMergeKey(envSource)] = envSource
  234. }
  235. mergedEnvFrom = append(mergedEnvFrom, envSources...)
  236. var errs []error
  237. for _, pp := range podPresets {
  238. for _, envFromSource := range pp.Spec.EnvFrom {
  239. internalEnvFrom := api.EnvFromSource{}
  240. if err := apiscorev1.Convert_v1_EnvFromSource_To_core_EnvFromSource(&envFromSource, &internalEnvFrom, nil); err != nil {
  241. return nil, err
  242. }
  243. found, ok := origEnvSources[newEnvFromMergeKey(internalEnvFrom)]
  244. if !ok {
  245. mergedEnvFrom = append(mergedEnvFrom, internalEnvFrom)
  246. continue
  247. }
  248. if !reflect.DeepEqual(found, internalEnvFrom) {
  249. errs = append(errs, fmt.Errorf("merging envFrom for %s has a conflict: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), internalEnvFrom, found))
  250. }
  251. }
  252. }
  253. err := utilerrors.NewAggregate(errs)
  254. if err != nil {
  255. return nil, err
  256. }
  257. return mergedEnvFrom, nil
  258. }
  259. // mergeVolumeMounts merges given list of VolumeMounts with the volumeMounts
  260. // injected by given podPresets. It returns an error if it detects any conflict during the merge.
  261. func mergeVolumeMounts(volumeMounts []api.VolumeMount, podPresets []*settingsv1alpha1.PodPreset) ([]api.VolumeMount, error) {
  262. origVolumeMounts := map[string]api.VolumeMount{}
  263. volumeMountsByPath := map[string]api.VolumeMount{}
  264. for _, v := range volumeMounts {
  265. origVolumeMounts[v.Name] = v
  266. volumeMountsByPath[v.MountPath] = v
  267. }
  268. mergedVolumeMounts := make([]api.VolumeMount, len(volumeMounts))
  269. copy(mergedVolumeMounts, volumeMounts)
  270. var errs []error
  271. for _, pp := range podPresets {
  272. for _, v := range pp.Spec.VolumeMounts {
  273. internalVolumeMount := api.VolumeMount{}
  274. if err := apiscorev1.Convert_v1_VolumeMount_To_core_VolumeMount(&v, &internalVolumeMount, nil); err != nil {
  275. return nil, err
  276. }
  277. found, ok := origVolumeMounts[v.Name]
  278. if !ok {
  279. // if we don't already have it append it and continue
  280. origVolumeMounts[v.Name] = internalVolumeMount
  281. mergedVolumeMounts = append(mergedVolumeMounts, internalVolumeMount)
  282. } else {
  283. // make sure they are identical or throw an error
  284. // shall we throw an error for identical volumeMounts ?
  285. if !reflect.DeepEqual(found, internalVolumeMount) {
  286. errs = append(errs, fmt.Errorf("merging volume mounts for %s has a conflict on %s: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), v.Name, v, found))
  287. }
  288. }
  289. found, ok = volumeMountsByPath[v.MountPath]
  290. if !ok {
  291. // if we don't already have it append it and continue
  292. volumeMountsByPath[v.MountPath] = internalVolumeMount
  293. } else {
  294. // make sure they are identical or throw an error
  295. if !reflect.DeepEqual(found, internalVolumeMount) {
  296. errs = append(errs, fmt.Errorf("merging volume mounts for %s has a conflict on mount path %s: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), v.MountPath, v, found))
  297. }
  298. }
  299. }
  300. }
  301. err := utilerrors.NewAggregate(errs)
  302. if err != nil {
  303. return nil, err
  304. }
  305. return mergedVolumeMounts, err
  306. }
  307. // mergeVolumes merges given list of Volumes with the volumes injected by given
  308. // podPresets. It returns an error if it detects any conflict during the merge.
  309. func mergeVolumes(volumes []api.Volume, podPresets []*settingsv1alpha1.PodPreset) ([]api.Volume, error) {
  310. origVolumes := map[string]api.Volume{}
  311. for _, v := range volumes {
  312. origVolumes[v.Name] = v
  313. }
  314. mergedVolumes := make([]api.Volume, len(volumes))
  315. copy(mergedVolumes, volumes)
  316. var errs []error
  317. for _, pp := range podPresets {
  318. for _, v := range pp.Spec.Volumes {
  319. internalVolume := api.Volume{}
  320. if err := apiscorev1.Convert_v1_Volume_To_core_Volume(&v, &internalVolume, nil); err != nil {
  321. return nil, err
  322. }
  323. found, ok := origVolumes[v.Name]
  324. if !ok {
  325. // if we don't already have it append it and continue
  326. origVolumes[v.Name] = internalVolume
  327. mergedVolumes = append(mergedVolumes, internalVolume)
  328. continue
  329. }
  330. // make sure they are identical or throw an error
  331. if !reflect.DeepEqual(found, internalVolume) {
  332. errs = append(errs, fmt.Errorf("merging volumes for %s has a conflict on %s: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), v.Name, v, found))
  333. }
  334. }
  335. }
  336. err := utilerrors.NewAggregate(errs)
  337. if err != nil {
  338. return nil, err
  339. }
  340. if len(mergedVolumes) == 0 {
  341. return nil, nil
  342. }
  343. return mergedVolumes, err
  344. }
  345. // applyPodPresetsOnPod updates the PodSpec with merged information from all the
  346. // applicable PodPresets. It ignores the errors of merge functions because merge
  347. // errors have already been checked in safeToApplyPodPresetsOnPod function.
  348. func applyPodPresetsOnPod(pod *api.Pod, podPresets []*settingsv1alpha1.PodPreset) {
  349. if len(podPresets) == 0 {
  350. return
  351. }
  352. volumes, _ := mergeVolumes(pod.Spec.Volumes, podPresets)
  353. pod.Spec.Volumes = volumes
  354. for i, ctr := range pod.Spec.Containers {
  355. applyPodPresetsOnContainer(&ctr, podPresets)
  356. pod.Spec.Containers[i] = ctr
  357. }
  358. for i, iCtr := range pod.Spec.InitContainers {
  359. applyPodPresetsOnContainer(&iCtr, podPresets)
  360. pod.Spec.InitContainers[i] = iCtr
  361. }
  362. // add annotation
  363. if pod.ObjectMeta.Annotations == nil {
  364. pod.ObjectMeta.Annotations = map[string]string{}
  365. }
  366. for _, pp := range podPresets {
  367. pod.ObjectMeta.Annotations[fmt.Sprintf("%s/podpreset-%s", annotationPrefix, pp.GetName())] = pp.GetResourceVersion()
  368. }
  369. }
  370. // applyPodPresetsOnContainer injects envVars, VolumeMounts and envFrom from
  371. // given podPresets in to the given container. It ignores conflict errors
  372. // because it assumes those have been checked already by the caller.
  373. func applyPodPresetsOnContainer(ctr *api.Container, podPresets []*settingsv1alpha1.PodPreset) {
  374. envVars, _ := mergeEnv(ctr.Env, podPresets)
  375. ctr.Env = envVars
  376. volumeMounts, _ := mergeVolumeMounts(ctr.VolumeMounts, podPresets)
  377. ctr.VolumeMounts = volumeMounts
  378. envFrom, _ := mergeEnvFrom(ctr.EnvFrom, podPresets)
  379. ctr.EnvFrom = envFrom
  380. }