Skip to content

Commit a1ca511

Browse files
committed
refactor git test server
1 parent a7ad981 commit a1ca511

File tree

4 files changed

+190
-128
lines changed

4 files changed

+190
-128
lines changed

go.mod

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ require (
1212
github.com/GoogleContainerTools/kaniko v1.9.2
1313
github.com/coder/envbuilder v1.0.0-rc.0.0.20240805094524-c1f9917dfb61
1414
github.com/docker/docker v26.1.4+incompatible
15-
github.com/docker/go-connections v0.5.0
15+
github.com/gliderlabs/ssh v0.3.7
1616
github.com/go-git/go-billy/v5 v5.5.0
1717
github.com/go-git/go-git/v5 v5.12.0
1818
github.com/google/go-containerregistry v0.19.1
@@ -60,6 +60,7 @@ require (
6060
github.com/agext/levenshtein v1.2.3 // indirect
6161
github.com/akutz/memconn v0.1.0 // indirect
6262
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect
63+
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
6364
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
6465
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c // indirect
6566
github.com/aws/aws-sdk-go-v2 v1.30.0 // indirect
@@ -117,6 +118,7 @@ require (
117118
github.com/docker/cli v26.1.0+incompatible // indirect
118119
github.com/docker/distribution v2.8.2+incompatible // indirect
119120
github.com/docker/docker-credential-helpers v0.8.1 // indirect
121+
github.com/docker/go-connections v0.5.0 // indirect
120122
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
121123
github.com/docker/go-metrics v0.0.1 // indirect
122124
github.com/docker/go-units v0.5.0 // indirect

internal/provider/cached_image_data_source_test.go

+10-7
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ import (
1818
func TestAccCachedImageDataSource(t *testing.T) {
1919
t.Run("Found", func(t *testing.T) {
2020
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
21-
t.Cleanup(cancel)
21+
defer cancel()
2222
files := map[string]string{
2323
".devcontainer/devcontainer.json": `{"build": { "dockerfile": "Dockerfile" }}`,
2424
".devcontainer/Dockerfile": `FROM localhost:5000/test-ubuntu:latest
2525
RUN apt-get update && apt-get install -y cowsay`,
2626
}
27-
deps := setup(t, files)
27+
28+
deps := setup(ctx, t, files)
2829
seedCache(ctx, t, deps)
2930
tfCfg := fmt.Sprintf(`data "envbuilder_cached_image" "test" {
3031
builder_image = %q
@@ -36,7 +37,7 @@ func TestAccCachedImageDataSource(t *testing.T) {
3637
}
3738
cache_repo = %q
3839
verbose = true
39-
}`, deps.BuilderImage, "/workspace", deps.RepoURL, deps.SSHKey, deps.CacheRepo)
40+
}`, deps.BuilderImage, "/workspace", deps.Repo.URL, deps.Repo.Key, deps.CacheRepo)
4041
resource.Test(t, resource.TestCase{
4142
PreCheck: func() { testAccPreCheck(t) },
4243
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
@@ -47,7 +48,7 @@ func TestAccCachedImageDataSource(t *testing.T) {
4748
// Inputs should still be present.
4849
resource.TestCheckResourceAttr("data.envbuilder_cached_image.test", "cache_repo", deps.CacheRepo),
4950
resource.TestCheckResourceAttr("data.envbuilder_cached_image.test", "extra_env.FOO", "bar"),
50-
resource.TestCheckResourceAttr("data.envbuilder_cached_image.test", "git_url", deps.RepoURL),
51+
resource.TestCheckResourceAttr("data.envbuilder_cached_image.test", "git_url", deps.Repo.URL),
5152
// Should be empty
5253
resource.TestCheckNoResourceAttr("data.envbuilder_cached_image.test", "git_username"),
5354
resource.TestCheckNoResourceAttr("data.envbuilder_cached_image.test", "git_password"),
@@ -79,12 +80,14 @@ func TestAccCachedImageDataSource(t *testing.T) {
7980
})
8081

8182
t.Run("NotFound", func(t *testing.T) {
83+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
84+
defer cancel()
8285
files := map[string]string{
8386
".devcontainer/devcontainer.json": `{"build": { "dockerfile": "Dockerfile" }}`,
8487
".devcontainer/Dockerfile": `FROM localhost:5000/test-ubuntu:latest
8588
RUN apt-get update && apt-get install -y cowsay`,
8689
}
87-
deps := setup(t, files)
90+
deps := setup(ctx, t, files)
8891
// We do not seed the cache.
8992
tfCfg := fmt.Sprintf(`data "envbuilder_cached_image" "test" {
9093
builder_image = %q
@@ -96,7 +99,7 @@ func TestAccCachedImageDataSource(t *testing.T) {
9699
}
97100
cache_repo = %q
98101
verbose = true
99-
}`, deps.BuilderImage, "/workspace", deps.RepoURL, deps.SSHKey, deps.CacheRepo)
102+
}`, deps.BuilderImage, "/workspace", deps.Repo.URL, deps.Repo.Key, deps.CacheRepo)
100103
resource.Test(t, resource.TestCase{
101104
PreCheck: func() { testAccPreCheck(t) },
102105
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
@@ -107,7 +110,7 @@ func TestAccCachedImageDataSource(t *testing.T) {
107110
// Inputs should still be present.
108111
resource.TestCheckResourceAttr("data.envbuilder_cached_image.test", "cache_repo", deps.CacheRepo),
109112
resource.TestCheckResourceAttr("data.envbuilder_cached_image.test", "extra_env.FOO", "bar"),
110-
resource.TestCheckResourceAttr("data.envbuilder_cached_image.test", "git_url", deps.RepoURL),
113+
resource.TestCheckResourceAttr("data.envbuilder_cached_image.test", "git_url", deps.Repo.URL),
111114
resource.TestCheckResourceAttr("data.envbuilder_cached_image.test", "exists", "false"),
112115
resource.TestCheckResourceAttr("data.envbuilder_cached_image.test", "image", deps.BuilderImage),
113116
// Should be empty

internal/provider/git_test.go

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"errors"
6+
"io"
7+
"net"
8+
"os"
9+
"os/exec"
10+
"path/filepath"
11+
"testing"
12+
13+
"github.com/gliderlabs/ssh"
14+
"github.com/go-git/go-git/v5"
15+
"github.com/go-git/go-git/v5/plumbing"
16+
"github.com/go-git/go-git/v5/plumbing/object"
17+
"github.com/stretchr/testify/assert"
18+
"github.com/stretchr/testify/require"
19+
)
20+
21+
// nolint:gosec // Throw-away key for testing. DO NOT REUSE.
22+
const (
23+
testSSHKey = `-----BEGIN OPENSSH PRIVATE KEY-----
24+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
25+
QyNTUxOQAAACCtxz9h0yXzi/HqZBpSkA2xFo28v5W8O4HimI0ZzNpQkwAAAKhv/+X2b//l
26+
9gAAAAtzc2gtZWQyNTUxOQAAACCtxz9h0yXzi/HqZBpSkA2xFo28v5W8O4HimI0ZzNpQkw
27+
AAAED/G0HuohvSa8q6NzkZ+wRPW0PhPpo9Th8fvcBQDaxCia3HP2HTJfOL8epkGlKQDbEW
28+
jby/lbw7geKYjRnM2lCTAAAAInRlcnJhZm9ybS1wcm92aWRlci1lbnZidWlsZGVyLXRlc3
29+
QBAgM=
30+
-----END OPENSSH PRIVATE KEY-----`
31+
testSSHPubKey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK3HP2HTJfOL8epkGlKQDbEWjby/lbw7geKYjRnM2lCT terraform-provider-envbuilder-test`
32+
)
33+
34+
func setupGitRepo(t testing.TB, files map[string]string) string {
35+
t.Helper()
36+
37+
dir := filepath.Join(t.TempDir(), "repo")
38+
39+
writeFiles(t, dir, files)
40+
41+
repo, err := git.PlainInitWithOptions(dir, &git.PlainInitOptions{
42+
InitOptions: git.InitOptions{
43+
DefaultBranch: plumbing.ReferenceName("refs/heads/main"),
44+
},
45+
})
46+
require.NoError(t, err, "init git repo")
47+
wt, err := repo.Worktree()
48+
require.NoError(t, err, "get worktree")
49+
_, err = wt.Add(".")
50+
require.NoError(t, err, "add files")
51+
_, err = wt.Commit("initial commit", &git.CommitOptions{
52+
Author: &object.Signature{
53+
Name: "test",
54+
55+
},
56+
})
57+
require.NoError(t, err, "commit files")
58+
t.Logf("initialized git repo at %s", dir)
59+
60+
return dir
61+
}
62+
63+
func writeFiles(t testing.TB, destPath string, files map[string]string) {
64+
t.Helper()
65+
66+
err := os.MkdirAll(destPath, 0o755)
67+
require.NoError(t, err, "create dest path")
68+
69+
for relPath, content := range files {
70+
absPath := filepath.Join(destPath, relPath)
71+
d := filepath.Dir(absPath)
72+
bs := []byte(content)
73+
require.NoError(t, os.MkdirAll(d, 0o755))
74+
require.NoError(t, os.WriteFile(absPath, bs, 0o644))
75+
t.Logf("wrote %d bytes to %s", len(bs), absPath)
76+
}
77+
}
78+
79+
type testGitRepoSSH struct {
80+
Dir string
81+
URL string
82+
Key string
83+
}
84+
85+
func serveGitRepoSSH(ctx context.Context, t testing.TB, dir string) testGitRepoSSH {
86+
t.Helper()
87+
88+
sshDir := filepath.Join(t.TempDir(), "ssh")
89+
require.NoError(t, os.Mkdir(sshDir, 0o700))
90+
91+
keyPath := filepath.Join(sshDir, "id_ed25519")
92+
require.NoError(t, os.WriteFile(keyPath, []byte(testSSHKey), 0o600))
93+
94+
// Start SSH server
95+
addr := startSSHServer(ctx, t, sshDir)
96+
97+
// Serve git repo
98+
repoURL := "ssh://" + addr + dir
99+
return testGitRepoSSH{
100+
Dir: dir,
101+
URL: repoURL,
102+
Key: keyPath,
103+
}
104+
}
105+
106+
func startSSHServer(ctx context.Context, t testing.TB, dir string) string {
107+
t.Helper()
108+
109+
s := &ssh.Server{
110+
PublicKeyHandler: func(ctx ssh.Context, key ssh.PublicKey) bool {
111+
return true // Allow all keys.
112+
},
113+
Handler: func(s ssh.Session) {
114+
t.Logf("session started: %s", s.RawCommand())
115+
116+
args := s.Command()
117+
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
118+
119+
in, err := cmd.StdinPipe()
120+
assert.NoError(t, err, "stdin pipe")
121+
out, err := cmd.StdoutPipe()
122+
assert.NoError(t, err, "stdout pipe")
123+
err = cmd.Start()
124+
if err != nil {
125+
t.Logf("command failed: %s", err)
126+
return
127+
}
128+
t.Cleanup(func() {
129+
_ = in.Close()
130+
_ = out.Close()
131+
_ = cmd.Process.Kill()
132+
})
133+
134+
go func() {
135+
_, _ = io.Copy(in, s)
136+
_ = in.Close()
137+
}()
138+
go func() {
139+
_, _ = io.Copy(s, out)
140+
_ = out.Close()
141+
_ = s.CloseWrite()
142+
}()
143+
err = cmd.Wait()
144+
if err != nil {
145+
t.Logf("command failed: %s", err)
146+
}
147+
148+
t.Logf("session ended: %s", s.RawCommand())
149+
150+
s.Exit(cmd.ProcessState.ExitCode())
151+
},
152+
}
153+
154+
ln, err := (&net.ListenConfig{}).Listen(ctx, "tcp", "localhost:0")
155+
require.NoError(t, err, "listen")
156+
157+
go func() {
158+
err := s.Serve(ln)
159+
if !errors.Is(err, ssh.ErrServerClosed) {
160+
require.NoError(t, err)
161+
}
162+
}()
163+
t.Cleanup(func() {
164+
_ = s.Close()
165+
_ = ln.Close()
166+
})
167+
168+
return ln.Addr().String()
169+
}

0 commit comments

Comments
 (0)