Introduce testutil for all the common test helpers
Move all the common test utilities that are needed for testing different packages into a common testutil package. Modify the test helpers to be more generic to be reusable. Signed-off-by: Sunny <github@darkowlzz.space>
This commit is contained in:
parent
e0d0885e32
commit
5b3b1e5fba
|
@ -0,0 +1,458 @@
|
|||
/*
|
||||
Copyright 2024 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/go-git/go-billy/v5/osfs"
|
||||
extgogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/cache"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
|
||||
"github.com/fluxcd/pkg/gittestserver"
|
||||
|
||||
"github.com/fluxcd/image-automation-controller/pkg/update"
|
||||
)
|
||||
|
||||
const (
|
||||
signingSecretKey = "git.asc"
|
||||
signingPassphraseKey = "passphrase"
|
||||
)
|
||||
|
||||
func CheckoutBranch(g *WithT, repo *extgogit.Repository, branch string) {
|
||||
g.THelper()
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branch),
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
func ReplaceMarker(path string, policyKey types.NamespacedName) error {
|
||||
return ReplaceMarkerWithMarker(path, policyKey, "SETTER_SITE")
|
||||
}
|
||||
|
||||
func ReplaceMarkerWithMarker(path string, policyKey types.NamespacedName, marker string) error {
|
||||
filebytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newfilebytes := bytes.ReplaceAll(filebytes, []byte(marker), []byte(setterRef(policyKey)))
|
||||
if err = os.WriteFile(path, newfilebytes, os.FileMode(0666)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setterRef(name types.NamespacedName) string {
|
||||
return fmt.Sprintf(`{"%s": "%s:%s"}`, update.SetterShortHand, name.Namespace, name.Name)
|
||||
}
|
||||
|
||||
func CommitInRepo(ctx context.Context, g *WithT, repoURL, branch, remote, msg string, changeFiles func(path string)) plumbing.Hash {
|
||||
g.THelper()
|
||||
|
||||
repo, cloneDir, err := Clone(ctx, repoURL, branch, remote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer func() { os.RemoveAll(cloneDir) }()
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
changeFiles(wt.Filesystem.Root())
|
||||
|
||||
id := CommitWorkDir(g, repo, branch, msg)
|
||||
|
||||
origin, err := repo.Remote(remote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(origin.Push(&extgogit.PushOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(BranchRefName(branch))},
|
||||
})).To(Succeed())
|
||||
return id
|
||||
}
|
||||
|
||||
func WaitForNewHead(g *WithT, repo *extgogit.Repository, branch, remote, preChangeHash string) {
|
||||
g.THelper()
|
||||
|
||||
var commitToResetTo *object.Commit
|
||||
|
||||
origin, err := repo.Remote(remote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Now try to fetch new commits from that remote branch
|
||||
g.Eventually(func() bool {
|
||||
err := origin.Fetch(&extgogit.FetchOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(BranchRefName(branch))},
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
err = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branch),
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
remoteHeadRef, err := repo.Head()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
remoteHeadHash := remoteHeadRef.Hash()
|
||||
|
||||
if preChangeHash != remoteHeadHash.String() {
|
||||
commitToResetTo, _ = repo.CommitObject(remoteHeadHash)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, 10*time.Second, time.Second).Should(BeTrue())
|
||||
|
||||
if commitToResetTo != nil {
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// New commits in the remote branch -- reset the working tree head
|
||||
// to that. Note this does not create a local branch tracking the
|
||||
// remote, so it is a detached head.
|
||||
g.Expect(wt.Reset(&extgogit.ResetOptions{
|
||||
Commit: commitToResetTo.Hash,
|
||||
Mode: extgogit.HardReset,
|
||||
})).To(Succeed())
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise a git server with a repo including the files in dir.
|
||||
func InitGitRepo(g *WithT, gitServer *gittestserver.GitServer, fixture, branch, repoPath string) *extgogit.Repository {
|
||||
g.THelper()
|
||||
|
||||
workDir, err := securejoin.SecureJoin(gitServer.Root(), repoPath)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
repo := InitGitRepoPlain(g, fixture, workDir)
|
||||
|
||||
headRef, err := repo.Head()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
ref := plumbing.NewHashReference(
|
||||
plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branch)),
|
||||
headRef.Hash())
|
||||
|
||||
g.Expect(repo.Storer.SetReference(ref)).ToNot(HaveOccurred())
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func InitGitRepoPlain(g *WithT, fixture, repoPath string) *extgogit.Repository {
|
||||
g.THelper()
|
||||
|
||||
wt := osfs.New(repoPath)
|
||||
dot := osfs.New(filepath.Join(repoPath, extgogit.GitDirName))
|
||||
storer := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
|
||||
|
||||
repo, err := extgogit.Init(storer, wt)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(copyDir(fixture, repoPath)).ToNot(HaveOccurred())
|
||||
|
||||
_ = CommitWorkDir(g, repo, "main", "Initial commit")
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func HeadFromBranch(repo *extgogit.Repository, branchName string) (*object.Commit, error) {
|
||||
ref, err := repo.Storer.Reference(plumbing.ReferenceName("refs/heads/" + branchName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo.CommitObject(ref.Hash())
|
||||
}
|
||||
|
||||
func CommitWorkDir(g *WithT, repo *extgogit.Repository, branchName, message string) plumbing.Hash {
|
||||
g.THelper()
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Checkout to an existing branch. If this is the first commit,
|
||||
// this is a no-op.
|
||||
_ = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.ReferenceName("refs/heads/" + branchName),
|
||||
})
|
||||
|
||||
status, err := wt.Status()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
for file := range status {
|
||||
wt.Add(file)
|
||||
}
|
||||
|
||||
sig := mockSignature(time.Now())
|
||||
c, err := wt.Commit(message, &extgogit.CommitOptions{
|
||||
All: true,
|
||||
Author: sig,
|
||||
Committer: sig,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
_, err = repo.Branch(branchName)
|
||||
if err == extgogit.ErrBranchNotFound {
|
||||
ref := plumbing.NewHashReference(
|
||||
plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branchName)), c)
|
||||
err = repo.Storer.SetReference(ref)
|
||||
}
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Now the target branch exists, we can checkout to it.
|
||||
err = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.ReferenceName("refs/heads/" + branchName),
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func TagCommit(g *WithT, repo *extgogit.Repository, commit plumbing.Hash, annotated bool, tag string, time time.Time) (*plumbing.Reference, error) {
|
||||
g.THelper()
|
||||
|
||||
var opts *extgogit.CreateTagOptions
|
||||
if annotated {
|
||||
opts = &extgogit.CreateTagOptions{
|
||||
Tagger: mockSignature(time),
|
||||
Message: "Annotated tag for: " + tag,
|
||||
}
|
||||
}
|
||||
return repo.CreateTag(tag, commit, opts)
|
||||
}
|
||||
|
||||
func copyDir(src string, dest string) error {
|
||||
file, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !file.IsDir() {
|
||||
return fmt.Errorf("source %q must be a directory", file.Name())
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(dest, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
srcFile := filepath.Join(src, f.Name())
|
||||
destFile := filepath.Join(dest, f.Name())
|
||||
|
||||
if f.IsDir() {
|
||||
if err = copyDir(srcFile, destFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.IsDir() {
|
||||
// ignore symlinks
|
||||
if f.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.WriteFile(destFile, content, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func BranchRefName(branch string) string {
|
||||
return fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)
|
||||
}
|
||||
|
||||
func mockSignature(time time.Time) *object.Signature {
|
||||
return &object.Signature{
|
||||
Name: "Jane Doe",
|
||||
Email: "author@example.com",
|
||||
When: time,
|
||||
}
|
||||
}
|
||||
|
||||
func Clone(ctx context.Context, repoURL, branchName, remote string) (*extgogit.Repository, string, error) {
|
||||
dir, err := os.MkdirTemp("", "iac-clone-*")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
opts := &extgogit.CloneOptions{
|
||||
URL: repoURL,
|
||||
RemoteName: remote,
|
||||
ReferenceName: plumbing.NewBranchReferenceName(branchName),
|
||||
}
|
||||
|
||||
wt := osfs.New(dir, osfs.WithBoundOS())
|
||||
dot := osfs.New(filepath.Join(dir, extgogit.GitDirName), osfs.WithBoundOS())
|
||||
storer := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
|
||||
|
||||
repo, err := extgogit.Clone(storer, wt, opts)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
w, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
err = w.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branchName),
|
||||
Create: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return repo, dir, nil
|
||||
}
|
||||
|
||||
func CommitIdFromBranch(repo *extgogit.Repository, branchName string) string {
|
||||
commitId := ""
|
||||
head, err := HeadFromBranch(repo, branchName)
|
||||
|
||||
if err == nil {
|
||||
commitId = head.Hash.String()
|
||||
}
|
||||
return commitId
|
||||
}
|
||||
|
||||
func GetRemoteHead(repo *extgogit.Repository, branchName, remote string) (plumbing.Hash, error) {
|
||||
rmt, err := repo.Remote(remote)
|
||||
if err != nil {
|
||||
return plumbing.ZeroHash, err
|
||||
}
|
||||
|
||||
err = rmt.Fetch(&extgogit.FetchOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(BranchRefName(branchName))},
|
||||
})
|
||||
if err != nil && !errors.Is(err, extgogit.NoErrAlreadyUpToDate) {
|
||||
return plumbing.ZeroHash, err
|
||||
}
|
||||
|
||||
remoteHeadRef, err := HeadFromBranch(repo, branchName)
|
||||
if err != nil {
|
||||
return plumbing.ZeroHash, err
|
||||
}
|
||||
|
||||
return remoteHeadRef.Hash, nil
|
||||
}
|
||||
|
||||
// SetUpGitTestServer creates and returns a git test server. The caller must
|
||||
// ensure it's stopped and cleaned up.
|
||||
func SetUpGitTestServer(g *WithT) *gittestserver.GitServer {
|
||||
g.THelper()
|
||||
|
||||
gitServer, err := gittestserver.NewTempGitServer()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
username := rand.String(5)
|
||||
password := rand.String(5)
|
||||
|
||||
gitServer.Auth(username, password)
|
||||
gitServer.AutoCreate()
|
||||
g.Expect(gitServer.StartHTTP()).ToNot(HaveOccurred())
|
||||
gitServer.KeyDir(filepath.Join(gitServer.Root(), "keys"))
|
||||
g.Expect(gitServer.ListenSSH()).ToNot(HaveOccurred())
|
||||
return gitServer
|
||||
}
|
||||
|
||||
func GetSigningKeyPairSecret(g *WithT, name, namespace string) (*corev1.Secret, *openpgp.Entity) {
|
||||
g.THelper()
|
||||
|
||||
passphrase := "abcde12345"
|
||||
pgpEntity, key := GetSigningKeyPair(g, passphrase)
|
||||
|
||||
// Create the secret containing signing key.
|
||||
sec := &corev1.Secret{
|
||||
Data: map[string][]byte{
|
||||
signingSecretKey: key,
|
||||
signingPassphraseKey: []byte(passphrase),
|
||||
},
|
||||
}
|
||||
sec.Name = name
|
||||
sec.Namespace = namespace
|
||||
return sec, pgpEntity
|
||||
}
|
||||
|
||||
func GetSigningKeyPair(g *WithT, passphrase string) (*openpgp.Entity, []byte) {
|
||||
g.THelper()
|
||||
|
||||
pgpEntity, err := openpgp.NewEntity("", "", "", nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Configure OpenPGP armor encoder.
|
||||
b := bytes.NewBuffer(nil)
|
||||
w, err := armor.Encode(b, openpgp.PrivateKeyType, nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
// Serialize private key.
|
||||
g.Expect(pgpEntity.SerializePrivate(w, nil)).To(Succeed())
|
||||
g.Expect(w.Close()).To(Succeed())
|
||||
|
||||
if passphrase != "" {
|
||||
g.Expect(pgpEntity.PrivateKey.Encrypt([]byte(passphrase))).To(Succeed())
|
||||
}
|
||||
|
||||
return pgpEntity, b.Bytes()
|
||||
}
|
Loading…
Reference in New Issue