From d12d62b99d4cf9083e7ef7813d6a85a437ba7c45 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 25 Nov 2024 00:24:08 -0600 Subject: [PATCH] feat: transform imagePullPolicy when using local cluster Signed-off-by: Lucas Rodriguez --- pkg/skaffold/deploy/kubectl/kubectl.go | 11 +++ .../kubernetes/manifest/image_pull_policy.go | 88 +++++++++++++++++++ .../manifest/image_pull_policy_test.go | 67 ++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 pkg/skaffold/kubernetes/manifest/image_pull_policy.go create mode 100644 pkg/skaffold/kubernetes/manifest/image_pull_policy_test.go diff --git a/pkg/skaffold/deploy/kubectl/kubectl.go b/pkg/skaffold/deploy/kubectl/kubectl.go index 1b5e8fdef37..63faeb870eb 100644 --- a/pkg/skaffold/deploy/kubectl/kubectl.go +++ b/pkg/skaffold/deploy/kubectl/kubectl.go @@ -241,6 +241,17 @@ func (k *Deployer) Deploy(ctx context.Context, out io.Writer, builds []graph.Art return err } + cluster, err := config.GetCluster(ctx, config.GetClusterOpts{}) + if err != nil { + return err + } + if cluster.Local { + manifests, err = manifests.ReplaceImagePullPolicy(manifest.NewResourceSelectorImagePullPolicy()) + if err != nil { + return err + } + } + childCtx, endTrace = instrumentation.StartTrace(ctx, "Deploy_LoadImages") if err := k.imageLoader.LoadImages(childCtx, out, k.localImages, k.originalImages, builds); err != nil { endTrace(instrumentation.TraceEndError(err)) diff --git a/pkg/skaffold/kubernetes/manifest/image_pull_policy.go b/pkg/skaffold/kubernetes/manifest/image_pull_policy.go new file mode 100644 index 00000000000..e03999578b5 --- /dev/null +++ b/pkg/skaffold/kubernetes/manifest/image_pull_policy.go @@ -0,0 +1,88 @@ +/* +Copyright 2019 The Skaffold Authors + +Licensed 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 manifest + +import apimachinery "k8s.io/apimachinery/pkg/runtime/schema" + +// resourceSelectorImagePullPolicy selects PodSpecs for transforming the imagePullPolicy field +// based on allowlist and denylist rules for their GroupKind and navigation path. +type resourceSelectorImagePullPolicy struct{} + +func NewResourceSelectorImagePullPolicy() ResourceSelector { + return &resourceSelectorImagePullPolicy{} +} + +// allowByGroupKind checks if a GroupKind is allowed for transformation. +// It allows GroupKinds in the allowlist unless they are in the denylist with ".*" in PodSpec paths, which blocks all PodSpecs. +func (rs *resourceSelectorImagePullPolicy) allowByGroupKind(gk apimachinery.GroupKind) bool { + if _, allowed := TransformAllowlist[gk]; allowed { + if rf, disallowed := TransformDenylist[gk]; disallowed { + for _, s := range rf.PodSpec { + if s == ".*" { + return false + } + } + } + return true + } + return false +} + +// allowByNavpath checks if a GroupKind's PodSpec path (navpath) allows transformation. +// It blocks transformation if the path matches a denylist entry or ".*". +// If not denied, it permits transformation if the path matches an allowlist entry or ".*". +func (rs *resourceSelectorImagePullPolicy) allowByNavpath(gk apimachinery.GroupKind, navpath string, k string) (string, bool) { + if rf, ok := TransformDenylist[gk]; ok { + for _, denypath := range rf.PodSpec { + if denypath == ".*" || navpath == denypath { + return "", false + } + } + } + if rf, ok := TransformAllowlist[gk]; ok { + for _, allowpath := range rf.PodSpec { + if allowpath == ".*" || navpath == allowpath { + return "", true + } + } + } + return "", false +} + +func (l *ManifestList) ReplaceImagePullPolicy(rs ResourceSelector) (ManifestList, error) { + r := &imagePullPolicyReplacer{} + return l.Visit(r, rs) +} + +// imagePullPolicyReplacer implements FieldVisitor and modifies the "imagePullPolicy" field in Kubernetes manifests. +type imagePullPolicyReplacer struct{} + +// Visit sets the value of the "imagePullPolicy" field in a Kubernetes manifest to "Never". +func (i *imagePullPolicyReplacer) Visit(gk apimachinery.GroupKind, navpath string, o map[string]interface{}, k string, v interface{}, rs ResourceSelector) bool { + const imagePullPolicyField = "imagePullPolicy" + if _, allowed := rs.allowByNavpath(gk, navpath, k); !allowed { + return true + } + if k != imagePullPolicyField { + return true + } + if _, ok := v.(string); !ok { + return true + } + o[imagePullPolicyField] = "Never" + return false +} diff --git a/pkg/skaffold/kubernetes/manifest/image_pull_policy_test.go b/pkg/skaffold/kubernetes/manifest/image_pull_policy_test.go new file mode 100644 index 00000000000..d1ff2a6bba4 --- /dev/null +++ b/pkg/skaffold/kubernetes/manifest/image_pull_policy_test.go @@ -0,0 +1,67 @@ +/* +Copyright 2019 The Skaffold Authors + +Licensed 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 manifest + +import ( + "testing" + + "github.com/GoogleContainerTools/skaffold/v2/testutil" +) + +func TestReplaceImagePullPolicy(t *testing.T) { + manifests := ManifestList{[]byte(` +apiVersion: v1 +kind: Pod +metadata: + name: replace-imagePullPolicy +spec: + containers: + - image: gcr.io/k8s-skaffold/example@sha256:81daf011d63b68cfa514ddab7741a1adddd59d3264118dfb0fd9266328bb8883 + name: if-not-present + imagePullPolicy: IfNotPresent + - image: gcr.io/k8s-skaffold/example@sha256:81daf011d63b68cfa514ddab7741a1adddd59d3264118dfb0fd9266328bb8883 + name: always + imagePullPolicy: Always + - image: gcr.io/k8s-skaffold/example@sha256:81daf011d63b68cfa514ddab7741a1adddd59d3264118dfb0fd9266328bb8883 + name: never + imagePullPolicy: Never +`)} + + expected := ManifestList{[]byte(` +apiVersion: v1 +kind: Pod +metadata: + name: replace-imagePullPolicy +spec: + containers: + - image: gcr.io/k8s-skaffold/example@sha256:81daf011d63b68cfa514ddab7741a1adddd59d3264118dfb0fd9266328bb8883 + name: if-not-present + imagePullPolicy: Never + - image: gcr.io/k8s-skaffold/example@sha256:81daf011d63b68cfa514ddab7741a1adddd59d3264118dfb0fd9266328bb8883 + name: always + imagePullPolicy: Never + - image: gcr.io/k8s-skaffold/example@sha256:81daf011d63b68cfa514ddab7741a1adddd59d3264118dfb0fd9266328bb8883 + name: never + imagePullPolicy: Never +`)} + + testutil.Run(t, "", func(t *testutil.T) { + resultManifest, err := manifests.ReplaceImagePullPolicy(NewResourceSelectorImagePullPolicy()) + t.CheckNoError(err) + t.CheckDeepEqual(expected.String(), resultManifest.String(), testutil.YamlObj(t.T)) + }) +}