admission.go 14 KB

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