Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go tar #2672

Merged
merged 2 commits into from
Jan 28, 2025
Merged

Go tar #2672

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile.utils
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
21 changes: 21 additions & 0 deletions cmd/func-util/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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() {
Expand All @@ -46,6 +50,8 @@ func main() {
cmd = s2iCmd
case "socat":
cmd = socat
case "sh":
cmd = sh
}

err := cmd(ctx)
Expand Down Expand Up @@ -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 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions pkg/k8s/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Binary file modified pkg/k8s/testdata/content.tar
Binary file not shown.
104 changes: 104 additions & 0 deletions pkg/tar/tar.go
Original file line number Diff line number Diff line change
@@ -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) {
Dismissed Show dismissed Hide dismissed
if first {
Dismissed Show dismissed Hide dismissed
// 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 changes: 169 additions & 0 deletions pkg/tar/tar_basic_test.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading
Loading