...
1
16
17
18
19
20
21
22
23
24
25 package alwayspullimages
26
27 import (
28 "context"
29 "io"
30
31 apierrors "k8s.io/apimachinery/pkg/api/errors"
32 utilerrors "k8s.io/apimachinery/pkg/util/errors"
33 "k8s.io/apimachinery/pkg/util/sets"
34 "k8s.io/apimachinery/pkg/util/validation/field"
35 "k8s.io/apiserver/pkg/admission"
36 "k8s.io/klog/v2"
37 api "k8s.io/kubernetes/pkg/apis/core"
38 "k8s.io/kubernetes/pkg/apis/core/pods"
39 )
40
41
42 const PluginName = "AlwaysPullImages"
43
44
45 func Register(plugins *admission.Plugins) {
46 plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
47 return NewAlwaysPullImages(), nil
48 })
49 }
50
51
52
53 type AlwaysPullImages struct {
54 *admission.Handler
55 }
56
57 var _ admission.MutationInterface = &AlwaysPullImages{}
58 var _ admission.ValidationInterface = &AlwaysPullImages{}
59
60
61 func (a *AlwaysPullImages) Admit(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
62
63 if shouldIgnore(attributes) {
64 return nil
65 }
66 pod, ok := attributes.GetObject().(*api.Pod)
67 if !ok {
68 return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
69 }
70
71 pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, _ *field.Path) bool {
72 c.ImagePullPolicy = api.PullAlways
73 return true
74 })
75
76 return nil
77 }
78
79
80 func (*AlwaysPullImages) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
81 if shouldIgnore(attributes) {
82 return nil
83 }
84
85 pod, ok := attributes.GetObject().(*api.Pod)
86 if !ok {
87 return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
88 }
89
90 var allErrs []error
91 pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, p *field.Path) bool {
92 if c.ImagePullPolicy != api.PullAlways {
93 allErrs = append(allErrs, admission.NewForbidden(attributes,
94 field.NotSupported(p.Child("imagePullPolicy"), c.ImagePullPolicy, []string{string(api.PullAlways)}),
95 ))
96 }
97 return true
98 })
99 if len(allErrs) > 0 {
100 return utilerrors.NewAggregate(allErrs)
101 }
102
103 return nil
104 }
105
106
107 func isUpdateWithNoNewImages(attributes admission.Attributes) bool {
108 if attributes.GetOperation() != admission.Update {
109 return false
110 }
111
112 pod, ok := attributes.GetObject().(*api.Pod)
113 if !ok {
114 klog.Warningf("Resource was marked with kind Pod but pod was unable to be converted.")
115 return false
116 }
117
118 oldPod, ok := attributes.GetOldObject().(*api.Pod)
119 if !ok {
120 klog.Warningf("Resource was marked with kind Pod but old pod was unable to be converted.")
121 return false
122 }
123
124 oldImages := sets.NewString()
125 pods.VisitContainersWithPath(&oldPod.Spec, field.NewPath("spec"), func(c *api.Container, _ *field.Path) bool {
126 oldImages.Insert(c.Image)
127 return true
128 })
129
130 hasNewImage := false
131 pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, _ *field.Path) bool {
132 if !oldImages.Has(c.Image) {
133 hasNewImage = true
134 }
135 return !hasNewImage
136 })
137 return !hasNewImage
138 }
139
140 func shouldIgnore(attributes admission.Attributes) bool {
141
142 if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
143 return true
144 }
145
146 if isUpdateWithNoNewImages(attributes) {
147 return true
148 }
149 return false
150 }
151
152
153 func NewAlwaysPullImages() *AlwaysPullImages {
154 return &AlwaysPullImages{
155 Handler: admission.NewHandler(admission.Create, admission.Update),
156 }
157 }
158
View as plain text