Skip to content

Commit 241d6a9

Browse files
authored
changed default image pull policy to if-not-present (#2600)
1 parent bf61b6b commit 241d6a9

File tree

11 files changed

+152
-84
lines changed

11 files changed

+152
-84
lines changed

e2e/testdata/fn-eval/error-in-pipe/.expected/config.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ stdErr: |
2121
Stderr:
2222
"[error] /// : failed to configure function: input namespace cannot be empty"
2323
Exit code: 1
24-
24+
2525
2626
[RUNNING] "gcr.io/kpt-fn/dne"
2727
[FAIL] "gcr.io/kpt-fn/dne" in 0s
28-
Error: Function image "gcr.io/kpt-fn/dne" doesn't exist
28+
Stderr:
29+
"docker: Error response from daemon: manifest for gcr.io/kpt-fn/dne:latest not found: manifest unknown: Failed to fetch \"latest\" from request \"/v2/kpt-fn/dne/manifests/latest\"."
30+
"See 'docker run --help'."
31+
Exit code: 125

e2e/testdata/fn-eval/fn-success-with-stderr/pkg/.expected/results.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@ metadata:
55
exitCode: 0
66
items:
77
- image: gcr.io/kpt-fn/starlark:v0.2
8-
stderr: |
9-
function succeeded, reporting it on stderr
8+
stderr: function succeeded, reporting it on stderr
109
exitCode: 0

e2e/testdata/fn-eval/missing-fn-image/.expected/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ exitCode: 1
1717
image: gcr.io/kpt-fn/dne # non-existing image
1818
args:
1919
namespace: staging
20-
stdErr: 'Function image "gcr.io/kpt-fn/dne" doesn''t exist'
20+
stdErr: 'gcr.io/kpt-fn/dne:latest not found'

e2e/testdata/fn-render/fn-success-with-stderr/.expected/results.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@ metadata:
55
exitCode: 0
66
items:
77
- image: gcr.io/kpt-fn/starlark:v0.2
8-
stderr: |
9-
function succeeded, reporting it on stderr
8+
stderr: function succeeded, reporting it on stderr
109
exitCode: 0

e2e/testdata/fn-render/missing-fn-image/.expected/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@
1313
# limitations under the License.
1414

1515
exitCode: 1
16-
stdErr: 'Error: Function image "gcr.io/kpt-fn/dne" doesn''t exist'
16+
stdErr: 'gcr.io/kpt-fn/dne:latest not found'

internal/cmdrender/cmdrender.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func NewRunner(ctx context.Context, parent string) *Runner {
4545
"path to a directory to save function results")
4646
c.Flags().StringVarP(&r.dest, "output", "o", "",
4747
fmt.Sprintf("output resources are written to provided location. Allowed values: %s|%s|<OUT_DIR_PATH>", cmdutil.Stdout, cmdutil.Unwrap))
48-
c.Flags().StringVar(&r.imagePullPolicy, "image-pull-policy", string(fnruntime.AlwaysPull),
48+
c.Flags().StringVar(&r.imagePullPolicy, "image-pull-policy", string(fnruntime.IfNotPresentPull),
4949
fmt.Sprintf("pull image before running the container. It must be one of %s, %s and %s.", fnruntime.AlwaysPull, fnruntime.IfNotPresentPull, fnruntime.NeverPull))
5050
cmdutil.FixDocs("kpt", parent, c)
5151
r.Command = c

internal/fnruntime/container.go

Lines changed: 60 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package fnruntime
1616

1717
import (
18+
"bufio"
1819
"bytes"
1920
"context"
2021
goerrors "errors"
@@ -34,11 +35,10 @@ import (
3435
type containerNetworkName string
3536

3637
const (
37-
networkNameNone containerNetworkName = "none"
38-
networkNameHost containerNetworkName = "host"
39-
defaultLongTimeout time.Duration = 5 * time.Minute
40-
defaultShortTimeout time.Duration = 5 * time.Second
41-
dockerBin string = "docker"
38+
networkNameNone containerNetworkName = "none"
39+
networkNameHost containerNetworkName = "host"
40+
defaultLongTimeout time.Duration = 5 * time.Minute
41+
dockerBin string = "docker"
4242

4343
AlwaysPull ImagePullPolicy = "Always"
4444
IfNotPresentPull ImagePullPolicy = "IfNotPresent"
@@ -85,13 +85,6 @@ type ContainerFn struct {
8585
// It reads the input from the given reader and writes the output
8686
// to the provided writer.
8787
func (f *ContainerFn) Run(reader io.Reader, writer io.Writer) error {
88-
// check and pull image before running to avoid polluting CLI
89-
// output
90-
err := f.prepareImage()
91-
if err != nil {
92-
return err
93-
}
94-
9588
errSink := bytes.Buffer{}
9689
cmd, cancel := f.getDockerCmd()
9790
defer cancel()
@@ -105,15 +98,15 @@ func (f *ContainerFn) Run(reader io.Reader, writer io.Writer) error {
10598
return &ExecError{
10699
OriginalErr: exitErr,
107100
ExitCode: exitErr.ExitCode(),
108-
Stderr: errSink.String(),
101+
Stderr: filterDockerCLIOutput(&errSink),
109102
TruncateOutput: printer.TruncateOutput,
110103
}
111104
}
112105
return fmt.Errorf("unexpected function error: %w", err)
113106
}
114107

115108
if errSink.Len() > 0 {
116-
f.FnResult.Stderr = errSink.String()
109+
f.FnResult.Stderr = filterDockerCLIOutput(&errSink)
117110
}
118111
return nil
119112
}
@@ -135,8 +128,15 @@ func (f *ContainerFn) getDockerCmd() (*exec.Cmd, context.CancelFunc) {
135128
"--user", uidgid,
136129
"--security-opt=no-new-privileges",
137130
}
138-
if f.ImagePullPolicy == NeverPull {
131+
switch f.ImagePullPolicy {
132+
case NeverPull:
139133
args = append(args, "--pull", "never")
134+
case AlwaysPull:
135+
args = append(args, "--pull", "pull")
136+
case IfNotPresentPull:
137+
args = append(args, "--pull", "missing")
138+
default:
139+
args = append(args, "--pull", "missing")
140140
}
141141
for _, storageMount := range f.StorageMounts {
142142
args = append(args, "--mount", storageMount.String())
@@ -173,62 +173,6 @@ func NewContainerEnvFromStringSlice(envStr []string) *runtimeutil.ContainerEnv {
173173
return ce
174174
}
175175

176-
// prepareImage will check local images and pull it if it doesn't
177-
// exist.
178-
func (f *ContainerFn) prepareImage() error {
179-
// If ImagePullPolicy is set to "never", we don't need to do anything here.
180-
if f.ImagePullPolicy == NeverPull {
181-
return nil
182-
}
183-
184-
// If ImagePullPolicy is set to "ifNotPresent", we scan the local images
185-
// first. If there is a match, we just return. This can be useful for local
186-
// development to prevent the remote image to accidentally override the
187-
// local image when they use the same name and tag.
188-
if f.ImagePullPolicy == IfNotPresentPull {
189-
if foundInLocalCache := f.checkImageExistence(); foundInLocalCache {
190-
return nil
191-
}
192-
}
193-
194-
// If ImagePullPolicy is set to always (which is the default), we will try
195-
// to pull the image regardless if the tag has been seen in the local cache.
196-
// This can help to ensure we have the latest release for "moving tags" like
197-
// v1 and v1.2. The performance cost is very minimal, since `docker pull`
198-
// checks the SHA first and only pull the missing docker layer(s).
199-
args := []string{"image", "pull", f.Image}
200-
// setup timeout
201-
timeout := defaultLongTimeout
202-
if f.Timeout != 0 {
203-
timeout = f.Timeout
204-
}
205-
ctx, cancel := context.WithTimeout(context.Background(), timeout)
206-
defer cancel()
207-
cmd := exec.CommandContext(ctx, dockerBin, args...)
208-
output, err := cmd.CombinedOutput()
209-
if err != nil {
210-
return &ContainerImageError{
211-
Image: f.Image,
212-
Output: string(output),
213-
}
214-
}
215-
return nil
216-
}
217-
218-
// checkImageExistence returns true if the image does exist in
219-
// local cache
220-
func (f *ContainerFn) checkImageExistence() bool {
221-
args := []string{"image", "inspect", f.Image}
222-
ctx, cancel := context.WithTimeout(context.Background(), defaultShortTimeout)
223-
defer cancel()
224-
cmd := exec.CommandContext(ctx, dockerBin, args...)
225-
if _, err := cmd.CombinedOutput(); err == nil {
226-
// image exists locally
227-
return true
228-
}
229-
return false
230-
}
231-
232176
// AddDefaultImagePathPrefix adds default gcr.io/kpt-fn/ path prefix to image if only image name is specified
233177
func AddDefaultImagePathPrefix(image string) string {
234178
if !strings.Contains(image, "/") {
@@ -250,3 +194,48 @@ func (e *ContainerImageError) Error() string {
250194
"Error: Function image %q doesn't exist remotely. If you are developing new functions locally, you can choose to set the image pull policy to ifNotPresent or never.\n%v",
251195
e.Image, e.Output)
252196
}
197+
198+
// filterDockerCLIOutput filters out docker CLI messages
199+
// from the given buffer.
200+
func filterDockerCLIOutput(in io.Reader) string {
201+
s := bufio.NewScanner(in)
202+
var lines []string
203+
204+
for s.Scan() {
205+
txt := s.Text()
206+
if !isdockerCLIoutput(txt) {
207+
lines = append(lines, txt)
208+
}
209+
}
210+
return strings.Join(lines, "\n")
211+
}
212+
213+
// isdockerCLIoutput is helper method to determine if
214+
// the given string is a docker CLI output message.
215+
// Example docker output:
216+
// "Unable to find image 'gcr.io/kpt-fn/starlark:v0.3' locally"
217+
// "v0.3: Pulling from kpt-fn/starlark"
218+
// "4e9f2cdf4387: Already exists"
219+
// "aafbf7df3ddf: Pulling fs layer"
220+
// "aafbf7df3ddf: Verifying Checksum"
221+
// "aafbf7df3ddf: Download complete"
222+
// "6b759ab96cb2: Waiting"
223+
// "aafbf7df3ddf: Pull complete"
224+
// "Digest: sha256:c347e28606fa1a608e8e02e03541a5a46e4a0152005df4a11e44f6c4ab1edd9a"
225+
// "Status: Downloaded newer image for gcr.io/kpt-fn/starlark:v0.3"
226+
//
227+
func isdockerCLIoutput(s string) bool {
228+
if strings.Contains(s, ": Already exists") ||
229+
strings.Contains(s, ": Pulling fs layer") ||
230+
strings.Contains(s, ": Verifying Checksum") ||
231+
strings.Contains(s, ": Download complete") ||
232+
strings.Contains(s, ": Pulling from") ||
233+
strings.Contains(s, ": Waiting") ||
234+
strings.Contains(s, ": Pull complete") ||
235+
strings.Contains(s, "Digest: sha256") ||
236+
strings.Contains(s, "Status: Downloaded newer image") ||
237+
strings.Contains(s, "Unable to find image") {
238+
return true
239+
}
240+
return false
241+
}

internal/fnruntime/container_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"github.com/GoogleContainerTools/kpt/internal/fnruntime"
2626
"github.com/GoogleContainerTools/kpt/internal/printer"
27+
fnresult "github.com/GoogleContainerTools/kpt/pkg/api/fnresult/v1"
2728
"github.com/stretchr/testify/assert"
2829
)
2930

@@ -54,6 +55,9 @@ func TestContainerFn(t *testing.T) {
5455
instance := fnruntime.ContainerFn{
5556
Ctx: printer.WithContext(ctx, printer.New(nil, errBuff)),
5657
Image: tt.image,
58+
FnResult: &fnresult.Result{
59+
Image: tt.image,
60+
},
5761
}
5862
input := bytes.NewBufferString(tt.input)
5963
output := &bytes.Buffer{}

internal/fnruntime/fnerrors_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package fnruntime
1616

1717
import (
18+
"bytes"
1819
"testing"
1920

2021
"github.com/stretchr/testify/assert"
@@ -138,3 +139,76 @@ error message`,
138139
})
139140
}
140141
}
142+
143+
func TestDockerCLIOutputFilter(t *testing.T) {
144+
145+
testcases := []struct {
146+
name string
147+
input string
148+
expected string
149+
}{
150+
{
151+
name: "should filter docker CLI output successfully",
152+
input: `Unable to find image 'gcr.io/kpt-fn/starlark:v0.3' locally
153+
v0.3: Pulling from kpt-fn/starlark
154+
4e9f2cdf4387: Already exists
155+
aafbf7df3ddf: Pulling fs layer
156+
aafbf7df3ddf: Verifying Checksum
157+
aafbf7df3ddf: Download complete
158+
aafbf7df3ddf: Pull complete
159+
6b759ab96cb2: Waiting
160+
Digest: sha256:c347e28606fa1a608e8e02e03541a5a46e4a0152005df4a11e44f6c4ab1edd9a
161+
Status: Downloaded newer image for gcr.io/kpt-fn/starlark:v0.3
162+
`,
163+
expected: "",
164+
},
165+
{
166+
name: "should filter docker messages and shouldn't truncate trailing lines",
167+
input: `Unable to find image 'gcr.io/kpt-fn/starlark:v0.3' locally
168+
v0.3: Pulling from kpt-fn/starlark
169+
4e9f2cdf4387: Already exists
170+
aafbf7df3ddf: Pulling fs layer
171+
aafbf7df3ddf: Verifying Checksum
172+
aafbf7df3ddf: Download complete
173+
aafbf7df3ddf: Pull complete
174+
6b759ab96cb2: Waiting
175+
Digest: sha256:c347e28606fa1a608e8e02e03541a5a46e4a0152005df4a11e44f6c4ab1edd9a
176+
Status: Downloaded newer image for gcr.io/kpt-fn/starlark:v0.3
177+
line before last line
178+
lastline
179+
180+
`,
181+
expected: `line before last line
182+
lastline
183+
`,
184+
},
185+
{
186+
name: "should filter interleaved docker messages",
187+
input: `firstline
188+
Unable to find image 'gcr.io/kpt-fn/starlark:v0.3' locally
189+
v0.3: Pulling from kpt-fn/starlark
190+
4e9f2cdf4387: Already exists
191+
aafbf7df3ddf: Pulling fs layer
192+
aafbf7df3ddf: Verifying Checksum
193+
line in the middle
194+
aafbf7df3ddf: Download complete
195+
aafbf7df3ddf: Pull complete
196+
6b759ab96cb2: Waiting
197+
Digest: sha256:c347e28606fa1a608e8e02e03541a5a46e4a0152005df4a11e44f6c4ab1edd9a
198+
Status: Downloaded newer image for gcr.io/kpt-fn/starlark:v0.3
199+
lastline
200+
`,
201+
expected: `firstline
202+
line in the middle
203+
lastline`,
204+
},
205+
}
206+
207+
for _, tc := range testcases {
208+
tc := tc
209+
t.Run(tc.name, func(t *testing.T) {
210+
s := filterDockerCLIOutput(bytes.NewBufferString(tc.input))
211+
assert.Equal(t, tc.expected, s)
212+
})
213+
}
214+
}

thirdparty/cmdconfig/commands/cmdeval/cmdeval.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func GetEvalFnRunner(ctx context.Context, parent string) *EvalFnRunner {
6161
"a list of environment variables to be used by functions")
6262
r.Command.Flags().BoolVar(
6363
&r.AsCurrentUser, "as-current-user", false, "use the uid and gid that kpt is running with to run the function in the container")
64-
r.Command.Flags().StringVar(&r.ImagePullPolicy, "image-pull-policy", string(fnruntime.AlwaysPull),
64+
r.Command.Flags().StringVar(&r.ImagePullPolicy, "image-pull-policy", string(fnruntime.IfNotPresentPull),
6565
fmt.Sprintf("pull image before running the container. It must be one of %s, %s and %s.", fnruntime.AlwaysPull, fnruntime.IfNotPresentPull, fnruntime.NeverPull))
6666
r.Command.Flags().StringVar(
6767
&r.Selector.APIVersion, "match-api-version", "", "select resources matching the given apiVersion")

0 commit comments

Comments
 (0)