* Fixed pod readiness check

Detect also if pod did not exited prematurely.

Signed-off-by: Matej Vašek <mvasek@redhat.com>

* Removed dependency on sh/tar from alpine image

This commit removes depencency on sh and tar binaries by implementing
the logic in our func-util binary.

Signed-off-by: Matej Vašek <mvasek@redhat.com>

---------

Signed-off-by: Matej Vašek <mvasek@redhat.com>
This commit is contained in:
Matej Vašek 2025-01-28 01:45:56 +01:00 committed by GitHub
parent 23668cbf36
commit 4de372564a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 617 additions and 1 deletions

View File

@ -34,6 +34,7 @@ COPY --from=builder /workspace/func-util /usr/local/bin/
RUN ln -s /usr/local/bin/func-util /usr/local/bin/deploy && \
ln -s /usr/local/bin/func-util /usr/local/bin/scaffold && \
ln -s /usr/local/bin/func-util /usr/local/bin/s2i && \
ln -s /usr/local/bin/func-util /usr/local/bin/sh && \
ln -s /usr/local/bin/func-util /usr/local/bin/socat
LABEL \

View File

@ -10,8 +10,11 @@ import (
"os"
"os/signal"
"path/filepath"
"slices"
"syscall"
"golang.org/x/sys/unix"
"github.com/openshift/source-to-image/pkg/cmd/cli"
"k8s.io/klog/v2"
@ -20,6 +23,7 @@ import (
"knative.dev/func/pkg/k8s"
"knative.dev/func/pkg/knative"
"knative.dev/func/pkg/scaffolding"
"knative.dev/func/pkg/tar"
)
func main() {
@ -46,6 +50,8 @@ func main() {
cmd = s2iCmd
case "socat":
cmd = socat
case "sh":
cmd = sh
}
err := cmd(ctx)
@ -167,3 +173,18 @@ func (d deployDecorator) UpdateLabels(function fn.Function, labels map[string]st
}
return labels
}
func sh(ctx context.Context) error {
if !slices.Equal(os.Args[1:], []string{"-c", "umask 0000 && exec tar -xmf -"}) {
return fmt.Errorf("this is a fake sh (only for backward compatiblility purposes)")
}
wd, err := os.Getwd()
if err != nil {
return fmt.Errorf("cannot get working directory: %w", err)
}
unix.Umask(0)
return tar.Extract(os.Stdin, wd)
}

2
go.mod
View File

@ -53,6 +53,7 @@ require (
golang.org/x/net v0.34.0
golang.org/x/oauth2 v0.24.0
golang.org/x/sync v0.10.0
golang.org/x/sys v0.29.0
golang.org/x/term v0.28.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
@ -272,7 +273,6 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.7.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect

View File

@ -423,6 +423,11 @@ func podReady(ctx context.Context, core v1.CoreV1Interface, podName, namespace s
d <- nil
return
}
if status.State.Terminated != nil {
msg, _ := GetPodLogs(ctx, namespace, podName, podName)
d <- fmt.Errorf("pod prematurely exited (output: %q, exitcode: %d)", msg, status.State.Terminated.ExitCode)
return
}
if status.State.Waiting != nil {
switch status.State.Waiting.Reason {
case "ErrImagePull",

Binary file not shown.

104
pkg/tar/tar.go Normal file
View File

@ -0,0 +1,104 @@
package tar
import (
"archive/tar"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"strings"
)
func Extract(input io.Reader, destDir string) error {
var err error
des, err := os.ReadDir(destDir)
if err != nil {
return fmt.Errorf("cannot read dest dir: %w", err)
}
for _, de := range des {
err = os.RemoveAll(filepath.Join(destDir, de.Name()))
if err != nil {
return fmt.Errorf("cannot purge dest dir: %w", err)
}
}
r := tar.NewReader(input)
var first bool = true
for {
var hdr *tar.Header
hdr, err = r.Next()
if err != nil {
if errors.Is(err, io.EOF) {
if first {
// mimic tar output on empty input
return fmt.Errorf("does not look like a tar")
}
return nil
}
return err
}
first = false
name := hdr.Name
linkname := hdr.Linkname
if strings.Contains(name, "..") {
return fmt.Errorf("name contains '..': %s", name)
}
if path.IsAbs(linkname) {
return fmt.Errorf("absolute symlink: %s->%s", name, linkname)
}
if strings.HasPrefix(path.Clean(path.Join(path.Dir(name), linkname)), "..") {
return fmt.Errorf("link target escapes: %s->%s", name, linkname)
}
var destPath, rel string
destPath = filepath.Join(destDir, filepath.FromSlash(name))
rel, err = filepath.Rel(destDir, destPath)
if err != nil {
return fmt.Errorf("cannot get relative path: %w", err)
}
if strings.HasPrefix(rel, "..") {
return fmt.Errorf("name escapes")
}
// ensure parent
err = os.MkdirAll(filepath.Dir(destPath), os.FileMode(hdr.Mode)&fs.ModePerm|0111)
if err != nil {
return fmt.Errorf("cannot ensure parent: %w", err)
}
switch {
case hdr.Typeflag == tar.TypeReg:
err = writeRegularFile(destPath, os.FileMode(hdr.Mode&0777), r)
case hdr.Typeflag == tar.TypeDir:
err = os.MkdirAll(destPath, os.FileMode(hdr.Mode)&fs.ModePerm)
case hdr.Typeflag == tar.TypeSymlink:
err = os.Symlink(linkname, destPath)
default:
_, _ = fmt.Printf("unsupported type flag: %d\n", hdr.Typeflag)
}
if err != nil {
return fmt.Errorf("cannot create entry: %w", err)
}
}
}
func writeRegularFile(target string, perm os.FileMode, content io.Reader) error {
f, err := os.OpenFile(target, os.O_CREATE|os.O_EXCL|os.O_WRONLY, perm)
if err != nil {
return err
}
defer func(f *os.File) {
_ = f.Close()
}(f)
_, err = io.Copy(f, content)
if err != nil {
return err
}
return nil
}

169
pkg/tar/tar_basic_test.go Normal file
View File

@ -0,0 +1,169 @@
package tar_test
import (
"archive/tar"
"bytes"
"io"
"os"
"path/filepath"
"testing"
tarutil "knative.dev/func/pkg/tar"
)
const (
aTxt1 = "a.txt first revision"
bTxt1 = "b.txt first revision"
aTxt2 = "a.txt second revision"
bTxt2 = "b.txt second revision"
)
func TestExtract(t *testing.T) {
var err error
d := t.TempDir()
err = tarutil.Extract(tarballV1(t), d)
if err != nil {
t.Fatal(err)
}
bs, err := os.ReadFile(filepath.Join(d, "dir/a.txt"))
if err != nil {
t.Fatal(err)
}
s := string(bs)
if s != aTxt1 {
t.Errorf("unexpected data: %s", s)
}
bs, err = os.ReadFile(filepath.Join(d, "dir/b.txt"))
if err != nil {
t.Fatal(err)
}
s = string(bs)
if s != bTxt1 {
t.Errorf("unexpected data: %s", s)
}
err = tarutil.Extract(tarballV2(t), d)
if err != nil {
t.Fatal(err)
}
bs, err = os.ReadFile(filepath.Join(d, "dir/a.txt"))
if err != nil {
t.Fatal(err)
}
s = string(bs)
if s != aTxt2 {
t.Errorf("unexpected data: %s", s)
}
bs, err = os.ReadFile(filepath.Join(d, "dir/b.txt"))
if err != nil {
t.Fatal(err)
}
s = string(bs)
if s != bTxt2 {
t.Errorf("unexpected data: %s", s)
}
}
func tarballV1(t *testing.T) io.Reader {
t.Helper()
var err error
var buff bytes.Buffer
w := tar.NewWriter(&buff)
defer func(w *tar.Writer) {
_ = w.Close()
}(w)
err = w.WriteHeader(&tar.Header{
Name: "dir/a.txt",
Typeflag: tar.TypeReg,
Mode: 0644,
Size: int64(len(aTxt1)),
})
if err != nil {
t.Fatal(err)
}
_, err = w.Write([]byte(aTxt1))
if err != nil {
t.Fatal(err)
}
err = w.WriteHeader(&tar.Header{
Name: "dir/data1",
Typeflag: tar.TypeReg,
Mode: 0644,
Size: int64(len(bTxt1)),
})
if err != nil {
t.Fatal(err)
}
_, err = w.Write([]byte(bTxt1))
if err != nil {
t.Fatal(err)
}
err = w.WriteHeader(&tar.Header{
Name: "dir/b.txt",
Linkname: "data1",
Typeflag: tar.TypeSymlink,
})
if err != nil {
t.Fatal(err)
}
return &buff
}
func tarballV2(t *testing.T) io.Reader {
t.Helper()
var err error
var buff bytes.Buffer
w := tar.NewWriter(&buff)
defer func(w *tar.Writer) {
_ = w.Close()
}(w)
err = w.WriteHeader(&tar.Header{
Name: "dir/a.txt",
Typeflag: tar.TypeReg,
Mode: 0644,
Size: int64(len(aTxt2)),
})
if err != nil {
t.Fatal(err)
}
_, err = w.Write([]byte(aTxt2))
if err != nil {
t.Fatal(err)
}
err = w.WriteHeader(&tar.Header{
Name: "dir/b.txt",
Linkname: "data2",
Typeflag: tar.TypeSymlink,
})
if err != nil {
t.Fatal(err)
}
err = w.WriteHeader(&tar.Header{
Name: "dir/data2",
Typeflag: tar.TypeReg,
Mode: 0644,
Size: int64(len(bTxt2)),
})
if err != nil {
t.Fatal(err)
}
_, err = w.Write([]byte(bTxt2))
if err != nil {
t.Fatal(err)
}
return &buff
}

316
pkg/tar/tar_errors_test.go Normal file
View File

@ -0,0 +1,316 @@
package tar_test
import (
"archive/tar"
"bytes"
"io"
"testing"
tarutil "knative.dev/func/pkg/tar"
)
func TestExtractErrors(t *testing.T) {
tests := []struct {
name string
createReader func(*testing.T) io.Reader
wantErr bool
}{
{
name: "non escaping link",
createReader: nonEscapingSymlink,
wantErr: false,
},
{
name: "missing parent of regular file",
createReader: missingParentRegular,
wantErr: false,
},
{
name: "missing parent of link",
createReader: missingParentLink,
wantErr: false,
},
{
name: "absolute symlink",
createReader: absoluteSymlink,
wantErr: true,
},
{
name: "direct escape",
createReader: directEscape,
wantErr: true,
},
{
name: "indirect link escape",
createReader: indirectLinkEscape,
wantErr: true,
},
{
name: "indirect link escape with overwrite",
createReader: indirectLinkEscapeWithOverwrites,
wantErr: true,
},
{
name: "double dot in name",
createReader: doubleDotInName,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := t.TempDir()
if err := tarutil.Extract(tt.createReader(t), d); (err != nil) != tt.wantErr {
t.Errorf("Extract() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func nonEscapingSymlink(t *testing.T) io.Reader {
t.Helper()
var err error
var buff bytes.Buffer
w := tar.NewWriter(&buff)
defer func(w *tar.Writer) {
_ = w.Close()
}(w)
err = w.WriteHeader(&tar.Header{
Name: "subdir",
Typeflag: tar.TypeDir,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
err = w.WriteHeader(&tar.Header{
Name: "subdir/parent",
Linkname: "..",
Typeflag: tar.TypeSymlink,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
return &buff
}
func absoluteSymlink(t *testing.T) io.Reader {
var err error
var buff bytes.Buffer
w := tar.NewWriter(&buff)
defer func(w *tar.Writer) {
_ = w.Close()
}(w)
err = w.WriteHeader(&tar.Header{
Name: "a.lnk",
Linkname: "/etc/shadow",
Typeflag: tar.TypeSymlink,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
return &buff
}
func directEscape(t *testing.T) io.Reader {
t.Helper()
var err error
var buff bytes.Buffer
var msg = "I am free!!!"
w := tar.NewWriter(&buff)
defer func(w *tar.Writer) {
_ = w.Close()
}(w)
err = w.WriteHeader(&tar.Header{
Name: "../escaped.txt",
Typeflag: tar.TypeReg,
Mode: 0644,
Size: int64(len(msg)),
})
if err != nil {
t.Fatal(err)
}
_, err = w.Write([]byte(msg))
if err != nil {
t.Fatal(err)
}
return &buff
}
func indirectLinkEscape(t *testing.T) io.Reader {
t.Helper()
t.Skip("we are not checking for this since the core utils tar does not too")
var err error
var buff bytes.Buffer
w := tar.NewWriter(&buff)
defer func(w *tar.Writer) {
_ = w.Close()
}(w)
err = w.WriteHeader(&tar.Header{
Name: "subdir",
Typeflag: tar.TypeDir,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
err = w.WriteHeader(&tar.Header{
Name: "subdir/parent",
Linkname: "..",
Typeflag: tar.TypeSymlink,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
err = w.WriteHeader(&tar.Header{
Name: "escape",
Linkname: "subdir/parent/..",
Typeflag: tar.TypeSymlink,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
return &buff
}
func indirectLinkEscapeWithOverwrites(t *testing.T) io.Reader {
t.Helper()
t.Skip("we are not checking for this since the core utils tar does not too")
var err error
var buff bytes.Buffer
w := tar.NewWriter(&buff)
defer func(w *tar.Writer) {
_ = w.Close()
}(w)
err = w.WriteHeader(&tar.Header{
Name: "subdir",
Typeflag: tar.TypeDir,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
err = w.WriteHeader(&tar.Header{
Name: "subdir/parent",
Typeflag: tar.TypeDir,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
err = w.WriteHeader(&tar.Header{
Name: "escape",
Linkname: "subdir/parent/..",
Typeflag: tar.TypeSymlink,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
err = w.WriteHeader(&tar.Header{
Name: "subdir/parent",
Linkname: "..",
Typeflag: tar.TypeSymlink,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
return &buff
}
func doubleDotInName(t *testing.T) io.Reader {
t.Helper()
var err error
var buff bytes.Buffer
w := tar.NewWriter(&buff)
defer func(w *tar.Writer) {
_ = w.Close()
}(w)
err = w.WriteHeader(&tar.Header{
Name: "subdir",
Typeflag: tar.TypeDir,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
err = w.WriteHeader(&tar.Header{
Name: "subdir/parent",
Typeflag: tar.TypeDir,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
err = w.WriteHeader(&tar.Header{
Name: "subdir/parent/../../a.txt",
Typeflag: tar.TypeReg,
Mode: 0644,
Size: 0,
})
if err != nil {
t.Fatal(err)
}
return &buff
}
func missingParentRegular(t *testing.T) io.Reader {
var err error
var buff bytes.Buffer
w := tar.NewWriter(&buff)
defer func(w *tar.Writer) {
_ = w.Close()
}(w)
err = w.WriteHeader(&tar.Header{
Name: "missing/a.txt",
Typeflag: tar.TypeReg,
Size: 0,
Mode: 0644,
})
if err != nil {
t.Fatal(err)
}
return &buff
}
func missingParentLink(t *testing.T) io.Reader {
var err error
var buff bytes.Buffer
w := tar.NewWriter(&buff)
defer func(w *tar.Writer) {
_ = w.Close()
}(w)
err = w.WriteHeader(&tar.Header{
Name: "missing/a.lnk",
Linkname: "a.txt",
Typeflag: tar.TypeSymlink,
Mode: 0777,
})
if err != nil {
t.Fatal(err)
}
return &buff
}