From a7fc1e49517c6275f6e4ccd226663d69290ce476 Mon Sep 17 00:00:00 2001 From: Alex Suraci Date: Fri, 23 Jun 2023 18:22:18 -0400 Subject: [PATCH] test HTTP and Git sources with network config Signed-off-by: Alex Suraci --- client/client_test.go | 267 +++++++++++++++++++++++++++++++++++ source/git/gitsource_unix.go | 89 +++++++----- 2 files changed, 319 insertions(+), 37 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index 82b667a17646a..2aa15075cd68d 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -2,6 +2,7 @@ package client import ( "archive/tar" + "bufio" "bytes" "compress/gzip" "context" @@ -239,6 +240,8 @@ func testIntegration(t *testing.T, funcs ...func(t *testing.T, sb integration.Sa integration.Run(t, integration.TestFuncs( testBridgeNetworkingDNSNoRootless, + testBridgeNetworkingDNSGitNoRootless, + testBridgeNetworkingDNSHTTPNoRootless, ), mirrors, integration.WithMatrix("netmode", map[string]interface{}{ @@ -356,6 +359,270 @@ func testBridgeNetworkingDNSNoRootless(t *testing.T, sb integration.Sandbox) { require.NoError(t, err) } +func testBridgeNetworkingDNSGitNoRootless(t *testing.T, sb integration.Sandbox) { + integration.CheckFeatureCompat(t, sb, integration.FeatureCNINetwork) + if os.Getenv("BUILDKIT_RUN_NETWORK_INTEGRATION_TESTS") == "" { + t.SkipNow() + } + + c, err := New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + serverBase := llb.Image("alpine:latest"). + Run(llb.Shlexf(`apk add git git-daemon`)). + File( + llb.Mkdir("/root/repo", 0755). + Mkfile("/root/repo/some-file", 0644, []byte(`hello`)), + ). + File(llb.Mkfile("/start.sh", 0755, []byte(`#!/bin/sh + +set -e -u -x + +cd /root + +git config --global user.email "root@localhost" +git config --global user.name "Test User" +git config --global init.defaultBranch main + +mkdir srv + +cd repo + git init + git add * + git commit -m "init" +cd .. + +cd srv + git clone --bare ../repo repo.git +cd .. + +git daemon --verbose --export-all --base-path=/root/srv +`))) + + myDomain := "mydomain" + testGit := func(ctx context.Context, gw gateway.Client) (*gateway.Result, error) { + def, err := serverBase.Marshal(ctx) + if err != nil { + return nil, err + } + + res, err := gw.Solve(ctx, gateway.SolveRequest{ + Definition: def.ToPB(), + }) + if err != nil { + return nil, err + } + + gitHost := identity.NewID() + gitCtr, err := gw.NewContainer(ctx, gateway.NewContainerRequest{ + Mounts: []gateway.Mount{ + { + Dest: "/", + MountType: pb.MountType_BIND, + Ref: res.Ref, + }, + }, + Hostname: gitHost + "." + myDomain, + }) + if err != nil { + return nil, err + } + defer gitCtr.Release(context.Background()) + + stderrR, stderrW := io.Pipe() + + proc, err := gitCtr.Start(ctx, gateway.StartRequest{ + Args: []string{"/start.sh"}, + Stderr: stderrW, + }) + if err != nil { + return nil, err + } + defer proc.Signal(ctx, syscall.SIGKILL) + + scan := bufio.NewScanner(stderrR) + for scan.Scan() { + line := scan.Text() + t.Log(line) + if strings.Contains(line, "Ready to rumble") { + t.Logf("git server is ready") + break + } + } + + gitSt := llb.Git( + "git://"+gitHost+"/repo.git", + "main", + llb.WithNetworkConfig("git-client"), + ) + + def, err = gitSt.Marshal(ctx) + if err != nil { + return nil, err + } + + res, err = gw.Solve(ctx, gateway.SolveRequest{ + Definition: def.ToPB(), + Evaluate: true, + }) + if err != nil { + return nil, err + } + + _, err = res.Ref.StatFile(ctx, gateway.StatRequest{ + Path: "some-file", + }) + if err != nil { + return nil, err + } + + return gateway.NewResult(), nil + } + + _, err = c.Build(sb.Context(), SolveOpt{ + Session: []session.Attachable{ + networks.NewConfigProvider(func(id string) *networks.Config { + switch id { + case "git-client": + return &networks.Config{ + Dns: &networks.DNSConfig{ + SearchDomains: []string{myDomain}, + }, + } + default: + panic("unknown network: " + id) + } + }), + }, + }, "", testGit, nil) + require.NoError(t, err) +} + +func testBridgeNetworkingDNSHTTPNoRootless(t *testing.T, sb integration.Sandbox) { + integration.CheckFeatureCompat(t, sb, integration.FeatureCNINetwork) + if os.Getenv("BUILDKIT_RUN_NETWORK_INTEGRATION_TESTS") == "" { + t.SkipNow() + } + + c, err := New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + serverBase := llb.Image("alpine:latest"). + Run(llb.Shlexf(`apk add python3`)). + File( + llb.Mkdir("/root/www", 0755). + Mkfile("/root/www/some-file", 0644, []byte(`hello`)), + ). + File(llb.Mkfile("/start.sh", 0755, []byte(`#!/bin/sh + +set -e -u -x + +cd /root/www + +python -m http.server 8000 +`))) + + myDomain := "mydomain" + testGit := func(ctx context.Context, gw gateway.Client) (*gateway.Result, error) { + def, err := serverBase.Marshal(ctx) + if err != nil { + return nil, err + } + + res, err := gw.Solve(ctx, gateway.SolveRequest{ + Definition: def.ToPB(), + }) + if err != nil { + return nil, err + } + + httpHost := identity.NewID() + httpCtr, err := gw.NewContainer(ctx, gateway.NewContainerRequest{ + Mounts: []gateway.Mount{ + { + Dest: "/", + MountType: pb.MountType_BIND, + Ref: res.Ref, + }, + }, + Hostname: httpHost + "." + myDomain, + }) + if err != nil { + return nil, err + } + defer httpCtr.Release(context.Background()) + + stdoutR, stdoutW := io.Pipe() + + proc, err := httpCtr.Start(ctx, gateway.StartRequest{ + Args: []string{"/start.sh"}, + Stdout: stdoutW, + Tty: true, + }) + if err != nil { + return nil, err + } + defer proc.Signal(ctx, syscall.SIGKILL) + + scan := bufio.NewScanner(stdoutR) + for scan.Scan() { + line := scan.Text() + t.Logf("from server: %s", line) + if strings.Contains(line, "Serving HTTP") { + t.Logf("HTTP server is ready") + break + } + } + + httpSt := llb.HTTP( + "http://"+httpHost+":8000/some-file", + llb.WithNetworkConfig("http-client"), + ) + + def, err = httpSt.Marshal(ctx) + if err != nil { + return nil, err + } + + res, err = gw.Solve(ctx, gateway.SolveRequest{ + Definition: def.ToPB(), + Evaluate: true, + }) + if err != nil { + return nil, err + } + + _, err = res.Ref.StatFile(ctx, gateway.StatRequest{ + Path: "some-file", + }) + if err != nil { + return nil, err + } + + return gateway.NewResult(), nil + } + + _, err = c.Build(sb.Context(), SolveOpt{ + Session: []session.Attachable{ + networks.NewConfigProvider(func(id string) *networks.Config { + switch id { + case "http-client": + return &networks.Config{ + Dns: &networks.DNSConfig{ + SearchDomains: []string{myDomain}, + }, + } + default: + panic("unknown network: " + id) + } + }), + }, + }, "", testGit, nil) + require.NoError(t, err) +} + func testHostNetworking(t *testing.T, sb integration.Sandbox) { if os.Getenv("BUILDKIT_RUN_NETWORK_INTEGRATION_TESTS") == "" { t.SkipNow() diff --git a/source/git/gitsource_unix.go b/source/git/gitsource_unix.go index 48c25d24c832d..a850f67f0893a 100644 --- a/source/git/gitsource_unix.go +++ b/source/git/gitsource_unix.go @@ -127,13 +127,13 @@ func (s *gitCLI) initConfig(netConf *networks.Config) error { } if len(netConf.IpHosts) > 0 { - if err := s.overrideHosts(netConf.IpHosts); err != nil { + if err := s.generateHosts(netConf.IpHosts); err != nil { return err } } if netConf.Dns != nil { - if err := s.overrideSearch(netConf.Dns); err != nil { + if err := s.generateResolv(netConf.Dns); err != nil { return err } } @@ -141,7 +141,7 @@ func (s *gitCLI) initConfig(netConf *networks.Config) error { return nil } -func (s *gitCLI) overrideHosts(extraHosts []*networks.IPHosts) error { +func (s *gitCLI) generateHosts(extraHosts []*networks.IPHosts) error { src, err := os.Open("/etc/hosts") if err != nil { return errors.Wrap(err, "read current hosts") @@ -156,14 +156,14 @@ func (s *gitCLI) overrideHosts(extraHosts []*networks.IPHosts) error { s.hostsPath = override.Name() - if err := replaceHosts(override, src, extraHosts); err != nil { + if err := mergeHosts(override, src, extraHosts); err != nil { return err } return override.Close() } -func (s *gitCLI) overrideSearch(dns *networks.DNSConfig) error { +func (s *gitCLI) generateResolv(dns *networks.DNSConfig) error { src, err := os.Open("/etc/resolv.conf") if err != nil { return err @@ -179,15 +179,14 @@ func (s *gitCLI) overrideSearch(dns *networks.DNSConfig) error { defer override.Close() - // TODO the rest - if err := replaceSearch(override, src, dns.SearchDomains); err != nil { + if err := mergeResolv(override, src, dns); err != nil { return err } return nil } -func replaceHosts(override *os.File, currentHosts io.Reader, extraHosts []*networks.IPHosts) error { +func mergeHosts(override *os.File, currentHosts io.Reader, extraHosts []*networks.IPHosts) error { if _, err := io.Copy(override, currentHosts); err != nil { return errors.Wrap(err, "write current hosts") } @@ -210,6 +209,51 @@ func replaceHosts(override *os.File, currentHosts io.Reader, extraHosts []*netwo return nil } +func mergeResolv(dst *os.File, src io.Reader, dns *networks.DNSConfig) error { + srcScan := bufio.NewScanner(src) + + var replacedSearch bool + var replacedOptions bool + + for _, ns := range dns.Nameservers { + fmt.Fprintln(dst, "nameserver", ns) + } + + for srcScan.Scan() { + switch { + case strings.HasPrefix(srcScan.Text(), "search"): + oldDomains := strings.Fields(srcScan.Text())[1:] + newDomains := append([]string{}, dns.SearchDomains...) + newDomains = append(newDomains, oldDomains...) + fmt.Fprintln(dst, "search", strings.Join(newDomains, " ")) + replacedSearch = true + case strings.HasPrefix(srcScan.Text(), "options"): + oldOptions := strings.Fields(srcScan.Text())[1:] + newOptions := append([]string{}, dns.Options...) + newOptions = append(newOptions, oldOptions...) + fmt.Fprintln(dst, "options", strings.Join(newOptions, " ")) + replacedOptions = true + case strings.HasPrefix(srcScan.Text(), "nameserver"): + if len(dns.Nameservers) == 0 { + // preserve existing nameservers + fmt.Fprintln(dst, srcScan.Text()) + } + default: + fmt.Fprintln(dst, srcScan.Text()) + } + } + + if !replacedSearch { + fmt.Fprintln(dst, "search", strings.Join(dns.SearchDomains, " ")) + } + + if !replacedOptions { + fmt.Fprintln(dst, "options", strings.Join(dns.Options, " ")) + } + + return nil +} + func runProcessGroup(ctx context.Context, cmd *exec.Cmd) error { cmd.Path = reexec.Self() cmd.Args = append([]string{gitCmd}, cmd.Args...) @@ -235,32 +279,3 @@ func runProcessGroup(ctx context.Context, cmd *exec.Cmd) error { close(waitDone) return err } - -func replaceSearch(dst *os.File, src io.Reader, searchDomains []string) error { - srcScan := bufio.NewScanner(src) - - var replaced bool - for srcScan.Scan() { - if !strings.HasPrefix(srcScan.Text(), "search") { - fmt.Fprintln(dst, srcScan.Text()) - continue - } - - oldDomains := strings.Fields(srcScan.Text())[1:] - - newDomains := append([]string{}, searchDomains...) - newDomains = append(newDomains, oldDomains...) - fmt.Fprintln(dst, "search", strings.Join(newDomains, " ")) - replaced = true - } - - if !replaced { - fmt.Fprintln(dst, "search", strings.Join(searchDomains, " ")) - } - - if err := mount.Mount(dst.Name(), "/etc/resolv.conf", "none", "bind,ro"); err != nil { - return errors.Wrap(err, "mount resolv.conf override") - } - - return nil -}