Skip to content

Commit 9c3ce89

Browse files
authored
Start In-Cluster Git Server For e2e Tests (#2823)
* Start In-Cluster Git Server For e2e Tests * Create git-server Deployment resource * Enable serving empty repository by default * Wait for Service to be ready
1 parent 5b5aa3e commit 9c3ce89

File tree

4 files changed

+267
-8
lines changed

4 files changed

+267
-8
lines changed

porch/apiserver/pkg/e2e/suite.go

Lines changed: 189 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"net/http"
2323
"os"
2424
"path/filepath"
25+
"strings"
2526
"testing"
2627
"time"
2728

@@ -32,9 +33,11 @@ import (
3233
"github.com/go-git/go-git/v5/plumbing"
3334
"github.com/go-git/go-git/v5/plumbing/object"
3435
"gopkg.in/yaml.v2"
36+
appsv1 "k8s.io/api/apps/v1"
3537
coreapi "k8s.io/api/core/v1"
3638
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3739
"k8s.io/apimachinery/pkg/runtime"
40+
"k8s.io/apimachinery/pkg/util/intstr"
3841
"k8s.io/client-go/rest"
3942
aggregatorv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
4043
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -153,8 +156,7 @@ func (t *TestSuite) CreateGitRepo() GitConfig {
153156
return createLocalGitServer(t.T)
154157
} else {
155158
// Deploy Git server via k8s client.
156-
t.Fatal("Creatig git server on k8s not yet supported.")
157-
return GitConfig{}
159+
return t.createInClusterGitServer()
158160
}
159161
}
160162

@@ -247,6 +249,7 @@ func createClientScheme(t *testing.T) *runtime.Scheme {
247249
configapi.AddToScheme,
248250
coreapi.AddToScheme,
249251
aggregatorv1.AddToScheme,
252+
appsv1.AddToScheme,
250253
}) {
251254
if err := api(scheme); err != nil {
252255
t.Fatalf("Failed to initialize test k8s api client")
@@ -356,3 +359,187 @@ func createInitialCommit(t *testing.T, repo *gogit.Repository) {
356359
t.Fatalf("Failed to set refs/heads/main to commit sha %s", commitHash)
357360
}
358361
}
362+
363+
func inferGitServerImage(porchImage string) string {
364+
slash := strings.LastIndex(porchImage, "/")
365+
repo := porchImage[:slash+1]
366+
image := porchImage[slash+1:]
367+
colon := strings.LastIndex(image, ":")
368+
tag := image[colon+1:]
369+
370+
return repo + "git-server:" + tag
371+
}
372+
373+
func (t *TestSuite) createInClusterGitServer() GitConfig {
374+
ctx := context.TODO()
375+
376+
// Determine git-server image name. Use the same container registry and tag as the Porch server,
377+
// replacing base image name with `git-server`. TODO: Make configurable?
378+
379+
var porch appsv1.Deployment
380+
t.GetF(ctx, client.ObjectKey{
381+
Namespace: "porch-system",
382+
Name: "porch-server",
383+
}, &porch)
384+
385+
gitImage := inferGitServerImage(porch.Spec.Template.Spec.Containers[0].Image)
386+
387+
var replicas int32 = 1
388+
var selector = strings.ReplaceAll(t.Name(), "/", "_")
389+
390+
t.CreateF(ctx, &appsv1.Deployment{
391+
ObjectMeta: metav1.ObjectMeta{
392+
Name: "git-server",
393+
Namespace: t.namespace,
394+
Annotations: map[string]string{
395+
"kpt.dev/porch-test": t.Name(),
396+
},
397+
},
398+
Spec: appsv1.DeploymentSpec{
399+
Replicas: &replicas,
400+
Selector: &metav1.LabelSelector{
401+
MatchLabels: map[string]string{
402+
"git-server": selector,
403+
},
404+
},
405+
Template: coreapi.PodTemplateSpec{
406+
ObjectMeta: metav1.ObjectMeta{
407+
Labels: map[string]string{
408+
"git-server": selector,
409+
},
410+
},
411+
Spec: coreapi.PodSpec{
412+
Containers: []coreapi.Container{
413+
{
414+
Name: "git-server",
415+
Image: gitImage,
416+
Args: []string{},
417+
Ports: []coreapi.ContainerPort{
418+
{
419+
ContainerPort: 8080,
420+
Protocol: coreapi.ProtocolTCP,
421+
},
422+
},
423+
ImagePullPolicy: coreapi.PullIfNotPresent,
424+
},
425+
},
426+
},
427+
},
428+
},
429+
})
430+
431+
t.Cleanup(func() {
432+
t.DeleteE(ctx, &appsv1.Deployment{
433+
ObjectMeta: metav1.ObjectMeta{
434+
Name: "git-server",
435+
Namespace: t.namespace,
436+
},
437+
})
438+
})
439+
440+
t.CreateF(ctx, &coreapi.Service{
441+
ObjectMeta: metav1.ObjectMeta{
442+
Name: "git-server-service",
443+
Namespace: t.namespace,
444+
Annotations: map[string]string{
445+
"kpt.dev/porch-test": t.Name(),
446+
},
447+
},
448+
Spec: coreapi.ServiceSpec{
449+
Ports: []coreapi.ServicePort{
450+
{
451+
Protocol: coreapi.ProtocolTCP,
452+
Port: 8080,
453+
TargetPort: intstr.IntOrString{
454+
Type: intstr.Int,
455+
IntVal: 8080,
456+
},
457+
},
458+
},
459+
Selector: map[string]string{
460+
"git-server": selector,
461+
},
462+
},
463+
})
464+
465+
t.Cleanup(func() {
466+
t.DeleteE(ctx, &coreapi.Service{
467+
ObjectMeta: metav1.ObjectMeta{
468+
Name: "git-server-service",
469+
Namespace: t.namespace,
470+
},
471+
})
472+
})
473+
474+
t.Logf("Waiting for git-server to start ...")
475+
476+
// Wait a minute for git server to start up.
477+
giveUp := time.Now().Add(time.Minute)
478+
479+
for {
480+
time.Sleep(5 * time.Second)
481+
482+
var server appsv1.Deployment
483+
t.GetF(ctx, client.ObjectKey{
484+
Namespace: t.namespace,
485+
Name: "git-server",
486+
}, &server)
487+
if server.Status.AvailableReplicas > 0 {
488+
t.Logf("git server is up")
489+
break
490+
}
491+
492+
if time.Now().After(giveUp) {
493+
t.Fatalf("git server failed to start: %s", &server)
494+
return GitConfig{}
495+
}
496+
}
497+
498+
t.Logf("Waiting for git-serever-service to be ready ...")
499+
500+
// Check the Endpoint resource for readiness
501+
giveUp = time.Now().Add(time.Minute)
502+
503+
for {
504+
time.Sleep(5 * time.Second)
505+
506+
var endpoint coreapi.Endpoints
507+
err := t.client.Get(ctx, client.ObjectKey{
508+
Namespace: t.namespace,
509+
Name: "git-server-service",
510+
}, &endpoint)
511+
512+
if err == nil && endpointIsReady(&endpoint) {
513+
t.Logf("git-server-service is ready")
514+
break
515+
}
516+
517+
if time.Now().After(giveUp) {
518+
t.Fatalf("git-server0-service not ready on time: %s", &endpoint)
519+
return GitConfig{}
520+
}
521+
}
522+
523+
return GitConfig{
524+
Repo: fmt.Sprintf("http://git-server-service.%s.svc.cluster.local:8080", t.namespace),
525+
Branch: "main",
526+
Directory: "/",
527+
}
528+
}
529+
530+
func endpointIsReady(endpoints *coreapi.Endpoints) bool {
531+
if len(endpoints.Subsets) == 0 {
532+
return false
533+
}
534+
for _, s := range endpoints.Subsets {
535+
if len(s.Addresses) == 0 {
536+
return false
537+
}
538+
for _, a := range s.Addresses {
539+
if a.IP == "" {
540+
return false
541+
}
542+
}
543+
}
544+
return true
545+
}

porch/engine/pkg/engine/clone_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func createRepoWithContents(t *testing.T, contentDir string) *gogit.Repository {
9999

100100
main := plumbing.NewHashReference(plumbing.ReferenceName("refs/heads/main"), hash)
101101
if err := repo.Storer.SetReference(main); err != nil {
102-
t.Fatalf("Failed to set refs/heads/main to commit sha %s", hash)
102+
t.Fatalf("Failed to set refs/heads/main to commit sha %s: %v", hash, err)
103103
}
104104
head := plumbing.NewSymbolicReference(plumbing.HEAD, "refs/heads/main")
105105
if err := repo.Storer.SetReference(head); err != nil {

porch/repository/pkg/git/testing.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func (s *GitServer) serveGitInfoRefs(w http.ResponseWriter, r *http.Request) err
151151
switch ref.Type() {
152152
case plumbing.SymbolicReference:
153153
if r, err := s.repo.Reference(ref.Name(), true); err != nil {
154-
klog.Warningf("Skippling unresolvable symbolic reference %q: %w", ref.Name(), err)
154+
klog.Warningf("Skipping unresolvable symbolic reference %q: %v", ref.Name(), err)
155155
return nil
156156
} else {
157157
resolved = r

porch/test/git/main.go

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@ import (
1818
"context"
1919
"flag"
2020
"fmt"
21+
"io/ioutil"
2122
"net"
2223
"net/http"
2324
"os"
2425
"os/signal"
26+
"time"
2527

2628
"github.com/GoogleContainerTools/kpt/porch/repository/pkg/git"
2729
gogit "github.com/go-git/go-git/v5"
30+
"github.com/go-git/go-git/v5/plumbing"
31+
"github.com/go-git/go-git/v5/plumbing/object"
2832
"k8s.io/klog/v2"
2933
)
3034

@@ -41,11 +45,22 @@ func main() {
4145
}
4246

4347
func run(dirs []string) error {
44-
if len(dirs) != 1 {
45-
return fmt.Errorf("expected one path to Git directory to serve. Got %d", len(dirs))
46-
}
48+
var dir string
49+
50+
switch len(dirs) {
51+
case 0:
52+
var err error
53+
dir, err = ioutil.TempDir("", "repo-*")
54+
if err != nil {
55+
return fmt.Errorf("failed to create temporary directory for git repository: %w", err)
56+
}
4757

48-
dir := dirs[0]
58+
case 1:
59+
dir = dirs[0]
60+
61+
default:
62+
return fmt.Errorf("can server only one git repository, not %d", len(dirs))
63+
}
4964

5065
var repo *gogit.Repository
5166
var err error
@@ -59,6 +74,12 @@ func run(dirs []string) error {
5974
if err != nil {
6075
return fmt.Errorf("failed to initialize git repository %q: %w", dir, err)
6176
}
77+
if err := createEmptyCommit(repo); err != nil {
78+
return err
79+
}
80+
81+
// Delete go-git default branch
82+
_ = repo.Storer.RemoveReference(plumbing.Master)
6283
}
6384

6485
server, err := git.NewGitServer(repo)
@@ -87,3 +108,54 @@ func run(dirs []string) error {
87108

88109
return nil
89110
}
111+
112+
func createEmptyCommit(repo *gogit.Repository) error {
113+
store := repo.Storer
114+
// Create first commit using empty tree.
115+
emptyTree := object.Tree{}
116+
encodedTree := store.NewEncodedObject()
117+
if err := emptyTree.Encode(encodedTree); err != nil {
118+
return fmt.Errorf("failed to encode initial empty commit tree: %w", err)
119+
}
120+
121+
treeHash, err := store.SetEncodedObject(encodedTree)
122+
if err != nil {
123+
return fmt.Errorf("failed to create initial empty commit tree: %w", err)
124+
}
125+
126+
sig := object.Signature{
127+
Name: "Git Server",
128+
129+
When: time.Now(),
130+
}
131+
132+
commit := object.Commit{
133+
Author: sig,
134+
Committer: sig,
135+
Message: "Empty Commit",
136+
TreeHash: treeHash,
137+
ParentHashes: []plumbing.Hash{}, // No parents
138+
}
139+
140+
encodedCommit := store.NewEncodedObject()
141+
if err := commit.Encode(encodedCommit); err != nil {
142+
return fmt.Errorf("failed to encode initial empty commit: %w", err)
143+
}
144+
145+
commitHash, err := store.SetEncodedObject(encodedCommit)
146+
if err != nil {
147+
return fmt.Errorf("failed to create initial empty commit: %w", err)
148+
}
149+
150+
main := plumbing.NewHashReference(plumbing.ReferenceName("refs/heads/main"), commitHash)
151+
if err := repo.Storer.SetReference(main); err != nil {
152+
return fmt.Errorf("failed to set refs/heads/main to commit sha %s: %w", commitHash, err)
153+
}
154+
155+
head := plumbing.NewSymbolicReference(plumbing.HEAD, "refs/heads/main")
156+
if err := repo.Storer.SetReference(head); err != nil {
157+
return fmt.Errorf("failed to set HEAD to refs/heads/main: %w", err)
158+
}
159+
160+
return nil
161+
}

0 commit comments

Comments
 (0)