Skip to content

Commit a5bf6a3

Browse files
committed
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>
1 parent 079db29 commit a5bf6a3

File tree

7 files changed

+695
-1
lines changed

7 files changed

+695
-1
lines changed

Dockerfile.utils

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ COPY --from=builder /workspace/func-util /usr/local/bin/
3434
RUN ln -s /usr/local/bin/func-util /usr/local/bin/deploy && \
3535
ln -s /usr/local/bin/func-util /usr/local/bin/scaffold && \
3636
ln -s /usr/local/bin/func-util /usr/local/bin/s2i && \
37+
ln -s /usr/local/bin/func-util /usr/local/bin/sh && \
3738
ln -s /usr/local/bin/func-util /usr/local/bin/socat
3839

3940
LABEL \

cmd/func-util/main.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ import (
1010
"os"
1111
"os/signal"
1212
"path/filepath"
13+
"slices"
1314
"syscall"
1415

16+
"golang.org/x/sys/unix"
17+
1518
"github.com/openshift/source-to-image/pkg/cmd/cli"
1619
"k8s.io/klog/v2"
1720

@@ -20,6 +23,7 @@ import (
2023
"knative.dev/func/pkg/k8s"
2124
"knative.dev/func/pkg/knative"
2225
"knative.dev/func/pkg/scaffolding"
26+
"knative.dev/func/pkg/tar"
2327
)
2428

2529
func main() {
@@ -46,6 +50,10 @@ func main() {
4650
cmd = s2iCmd
4751
case "socat":
4852
cmd = socat
53+
case "sh":
54+
cmd = sh
55+
default:
56+
cmd = sh
4957
}
5058

5159
err := cmd(ctx)
@@ -167,3 +175,18 @@ func (d deployDecorator) UpdateLabels(function fn.Function, labels map[string]st
167175
}
168176
return labels
169177
}
178+
179+
func sh(ctx context.Context) error {
180+
if !slices.Equal(os.Args[1:], []string{"-c", "umask 0000 && exec tar -xmf -"}) {
181+
return fmt.Errorf("this is a fake sh (only for backward compatiblility purposes)")
182+
}
183+
184+
wd, err := os.Getwd()
185+
if err != nil {
186+
return fmt.Errorf("cannot get working directory: %w", err)
187+
}
188+
189+
unix.Umask(0)
190+
191+
return tar.Extract(os.Stdin, wd)
192+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ require (
5353
golang.org/x/net v0.34.0
5454
golang.org/x/oauth2 v0.24.0
5555
golang.org/x/sync v0.10.0
56+
golang.org/x/sys v0.29.0
5657
golang.org/x/term v0.28.0
5758
gopkg.in/yaml.v2 v2.4.0
5859
gopkg.in/yaml.v3 v3.0.1
@@ -272,7 +273,6 @@ require (
272273
go.uber.org/multierr v1.11.0 // indirect
273274
go.uber.org/zap v1.27.0 // indirect
274275
golang.org/x/mod v0.22.0 // indirect
275-
golang.org/x/sys v0.29.0 // indirect
276276
golang.org/x/text v0.21.0 // indirect
277277
golang.org/x/time v0.7.0 // indirect
278278
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect

pkg/k8s/testdata/content.tar

0 Bytes
Binary file not shown.

pkg/tar/tar.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package tar
2+
3+
import (
4+
"archive/tar"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"io/fs"
9+
"os"
10+
"path"
11+
"path/filepath"
12+
"strings"
13+
)
14+
15+
func Extract(input io.Reader, targetDir string) error {
16+
var err error
17+
18+
r := tar.NewReader(input)
19+
20+
var first bool = true
21+
for {
22+
var hdr *tar.Header
23+
hdr, err = r.Next()
24+
if err != nil {
25+
if errors.Is(err, io.EOF) {
26+
if first {
27+
// mimic tar output on empty input
28+
return fmt.Errorf("does not look like a tar")
29+
}
30+
return nil
31+
}
32+
return err
33+
}
34+
35+
if strings.Contains(hdr.Name, "..") {
36+
return fmt.Errorf("name contains '..': %s", hdr.Name)
37+
}
38+
if path.IsAbs(hdr.Linkname) {
39+
return fmt.Errorf("absolute symlink: %s->%s", hdr.Name, hdr.Linkname)
40+
}
41+
if strings.HasPrefix(path.Clean(path.Join(path.Dir(hdr.Name), hdr.Linkname)), "..") {
42+
return fmt.Errorf("link target escapes: %s->%s", hdr.Name, hdr.Linkname)
43+
}
44+
45+
var targetPath, rel string
46+
targetPath = filepath.Join(targetDir, filepath.FromSlash(hdr.Name))
47+
rel, err = filepath.Rel(targetDir, targetPath)
48+
if err != nil {
49+
return fmt.Errorf("cannot get relative path: %w", err)
50+
}
51+
if strings.HasPrefix(rel, "..") {
52+
return fmt.Errorf("name escapes")
53+
}
54+
55+
// remove if already exists
56+
err = os.Remove(targetPath)
57+
if err != nil && !errors.Is(err, os.ErrNotExist) {
58+
return fmt.Errorf("cannot remove: %w", err)
59+
}
60+
61+
// ensure parent
62+
err = os.MkdirAll(filepath.Dir(targetPath), os.FileMode(hdr.Mode)&fs.ModePerm|0111)
63+
if err != nil {
64+
return fmt.Errorf("cannot ensure parent: %w", err)
65+
}
66+
67+
first = false
68+
switch {
69+
case hdr.Typeflag == tar.TypeReg:
70+
err = writeRegularFile(targetPath, os.FileMode(hdr.Mode&0777), r)
71+
case hdr.Typeflag == tar.TypeDir:
72+
err = os.MkdirAll(targetPath, os.FileMode(hdr.Mode)&fs.ModePerm)
73+
case hdr.Typeflag == tar.TypeSymlink:
74+
err = os.Symlink(hdr.Linkname, targetPath)
75+
default:
76+
_, _ = fmt.Printf("unsupported type flag: %d\n", hdr.Typeflag)
77+
}
78+
if err != nil {
79+
return fmt.Errorf("cannot create entry: %w", err)
80+
}
81+
}
82+
}
83+
84+
func writeRegularFile(target string, perm os.FileMode, content io.Reader) error {
85+
f, err := os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)
86+
if err != nil {
87+
return err
88+
}
89+
defer func(f *os.File) {
90+
_ = f.Close()
91+
}(f)
92+
_, err = io.Copy(f, content)
93+
if err != nil {
94+
return err
95+
}
96+
return nil
97+
}

pkg/tar/tar_basic_test.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package tar_test
2+
3+
import (
4+
"archive/tar"
5+
"bytes"
6+
"io"
7+
"os"
8+
"path/filepath"
9+
"testing"
10+
11+
tarutil "knative.dev/func/pkg/tar"
12+
)
13+
14+
func TestExtract(t *testing.T) {
15+
var err error
16+
d := t.TempDir()
17+
err = tarutil.Extract(sourceV1(t), d)
18+
if err != nil {
19+
t.Fatal(err)
20+
}
21+
22+
bs, err := os.ReadFile(filepath.Join(d, "dir/a.txt"))
23+
if err != nil {
24+
t.Fatal(err)
25+
}
26+
s := string(bs)
27+
if s != a1 {
28+
t.Errorf("unexpected data: %s", s)
29+
}
30+
bs, err = os.ReadFile(filepath.Join(d, "dir/b.txt"))
31+
if err != nil {
32+
t.Fatal(err)
33+
}
34+
s = string(bs)
35+
if s != b1 {
36+
t.Errorf("unexpected data: %s", s)
37+
}
38+
39+
err = tarutil.Extract(sourceV2(t), d)
40+
if err != nil {
41+
t.Fatal(err)
42+
}
43+
44+
bs, err = os.ReadFile(filepath.Join(d, "dir/a.txt"))
45+
if err != nil {
46+
t.Fatal(err)
47+
}
48+
s = string(bs)
49+
if s != a2 {
50+
t.Errorf("unexpected data: %s", s)
51+
}
52+
bs, err = os.ReadFile(filepath.Join(d, "dir/b.txt"))
53+
if err != nil {
54+
t.Fatal(err)
55+
}
56+
s = string(bs)
57+
if s != b2 {
58+
t.Errorf("unexpected data: %s", s)
59+
}
60+
}
61+
62+
const (
63+
a1 = "123"
64+
b1 = "456"
65+
a2 = "678"
66+
b2 = "901"
67+
)
68+
69+
func sourceV1(t *testing.T) io.Reader {
70+
t.Helper()
71+
72+
var err error
73+
var buff bytes.Buffer
74+
75+
w := tar.NewWriter(&buff)
76+
defer func(w *tar.Writer) {
77+
_ = w.Close()
78+
}(w)
79+
80+
err = w.WriteHeader(&tar.Header{
81+
Name: "dir/a.txt",
82+
Typeflag: tar.TypeReg,
83+
Mode: 0644,
84+
Size: int64(len(a1)),
85+
})
86+
if err != nil {
87+
t.Fatal(err)
88+
}
89+
_, err = w.Write([]byte(a1))
90+
if err != nil {
91+
t.Fatal(err)
92+
}
93+
94+
err = w.WriteHeader(&tar.Header{
95+
Name: "dir/data1",
96+
Typeflag: tar.TypeReg,
97+
Mode: 0644,
98+
Size: int64(len(b1)),
99+
})
100+
if err != nil {
101+
t.Fatal(err)
102+
}
103+
_, err = w.Write([]byte(b1))
104+
if err != nil {
105+
t.Fatal(err)
106+
}
107+
108+
err = w.WriteHeader(&tar.Header{
109+
Name: "dir/data2",
110+
Typeflag: tar.TypeReg,
111+
Mode: 0644,
112+
Size: int64(len(b2)),
113+
})
114+
if err != nil {
115+
t.Fatal(err)
116+
}
117+
_, err = w.Write([]byte(b2))
118+
if err != nil {
119+
t.Fatal(err)
120+
}
121+
122+
err = w.WriteHeader(&tar.Header{
123+
Name: "dir/b.txt",
124+
Linkname: "data1",
125+
Typeflag: tar.TypeSymlink,
126+
})
127+
if err != nil {
128+
t.Fatal(err)
129+
}
130+
131+
return &buff
132+
}
133+
134+
func sourceV2(t *testing.T) io.Reader {
135+
t.Helper()
136+
137+
var err error
138+
var buff bytes.Buffer
139+
140+
w := tar.NewWriter(&buff)
141+
defer func(w *tar.Writer) {
142+
_ = w.Close()
143+
}(w)
144+
145+
err = w.WriteHeader(&tar.Header{
146+
Name: "dir/a.txt",
147+
Typeflag: tar.TypeReg,
148+
Mode: 0644,
149+
Size: int64(len(a2)),
150+
})
151+
if err != nil {
152+
t.Fatal(err)
153+
}
154+
_, err = w.Write([]byte(a2))
155+
if err != nil {
156+
t.Fatal(err)
157+
}
158+
159+
err = w.WriteHeader(&tar.Header{
160+
Name: "dir/b.txt",
161+
Linkname: "data2",
162+
Typeflag: tar.TypeSymlink,
163+
})
164+
if err != nil {
165+
t.Fatal(err)
166+
}
167+
return &buff
168+
}

0 commit comments

Comments
 (0)