package main import ( "archive/tar" "bytes" "context" "errors" "strings" "testing" ) func archiveOf(t *testing.T, h tar.Header, body string) string { t.Helper() var buf bytes.Buffer w := tar.NewWriter(&buf) h.Size = int64(len(body)) if h.Typeflag == tar.TypeSymlink { h.Size = 0 } if err := w.WriteHeader(&h); err != nil { t.Fatal(err) } if h.Typeflag != tar.TypeSymlink { if _, err := w.Write([]byte(body)); err != nil { t.Fatal(err) } } w.Close() return buf.String() } // Issue 348: route-proxy asked 10.10.0.1 alone while the machine listed both mesh resolvers; its copy // of the file could not be read by anything on the mesh. func TestAContainersResolverFileIsReadThroughTheArchiveAndComparedWithTheMachines(t *testing.T) { inside := "# Generated by Docker Engine.\nnameserver 10.10.0.1\nsearch novox.internal\noptions timeout:1 attempts:2\n" f := (&fake{}).on("docker cp route-proxy:/etc/resolv.conf -", Ran{Stdout: archiveOf(t, tar.Header{Name: "resolv.conf", Typeflag: tar.TypeReg, Mode: 0o644}, inside)}) c := client(f, 1000) c.ReadFile = func(p string) ([]byte, error) { if p != HostResolvConf { t.Fatalf("read %s", p) } return []byte("nameserver 10.10.0.1\nnameserver 10.10.0.2\noptions timeout:1 attempts:2 edns0\n"), nil } got, err := c.ResolvConfOf(context.Background(), "route-proxy") if err != nil { t.Fatal(err) } rc := got["resolv_conf"].(ResolvConf) if strings.Join(rc.Nameservers, ",") != "10.10.0.1" || strings.Join(rc.Search, ",") != "novox.internal" || strings.Join(rc.Options, ",") != "timeout:1,attempts:2" || rc.Raw != inside { t.Fatalf("parsed: %+v", rc) } if got["same_as_machine"] != false || got["note"] == nil { t.Fatalf("a copy listing fewer resolvers than the machine is not said: %v", got) } if f.ran("docker exec") { t.Fatal("read by exec, not through the archive") } } func TestAResolverFileThatCannotBeReadIsSaidPlainly(t *testing.T) { c := client((&fake{}).on("docker cp", Ran{Status: 1, Stderr: "Error response from daemon: No such container: nope\n"}), 1000) if _, err := c.ResolvConfOf(context.Background(), "nope"); err == nil || !strings.Contains(err.Error(), "could not be read") { t.Fatalf("a missing container: %v", err) } link := archiveOf(t, tar.Header{Name: "resolv.conf", Typeflag: tar.TypeSymlink, Linkname: "/run/systemd/resolve/stub-resolv.conf"}, "") c = client((&fake{}).on("docker cp", Ran{Stdout: link}), 1000) if _, err := c.ResolvConfOf(context.Background(), "x"); err == nil || !strings.Contains(err.Error(), "link to") { t.Fatalf("a link inside: %v", err) } c = client((&fake{}).on("docker cp", Ran{Stdout: "not a tar"}), 1000) if _, err := c.ResolvConfOf(context.Background(), "x"); err == nil { t.Fatal("garbage was read as a file") } ok := archiveOf(t, tar.Header{Name: "resolv.conf", Typeflag: tar.TypeReg, Mode: 0o644}, "nameserver 10.10.0.1\n") c = client((&fake{}).on("docker cp", Ran{Stdout: ok}), 1000) c.ReadFile = func(string) ([]byte, error) { return nil, errors.New("permission denied") } got, err := c.ResolvConfOf(context.Background(), "x") if err != nil || !strings.Contains(got["machine"].(string), "could not be read") || got["same_as_machine"] != nil { t.Fatalf("an unreadable machine file: %v, %v", got, err) } } // The container is named as docker knows it, never as something docker would read as an option. func TestAResolverFileIsAskedOnlyOfAContainerRef(t *testing.T) { f := &fake{} for _, ref := range []string{"--privileged", "a b", "", "x;rm"} { if _, err := client(f, 1000).ResolvConfOf(context.Background(), ref); err == nil { t.Errorf("%q was asked of the runtime", ref) } } if len(f.calls) != 0 { t.Fatalf("the runtime was asked: %+v", f.calls) } }