Rearrange the protocol x implementation tests

There is a core chuck of testing that is repeated for {SSH,HTTP} x
{go-git,libgit2}, which is done by repeating a func value in different
contexts. Instead of mutating variables in the func's closure, it's a
bit clearer (and shorter) to pass them to a higher-order func.

Signed-off-by: Michael Bridgen <michael@weave.works>
This commit is contained in:
Michael Bridgen 2021-03-01 19:09:37 +00:00
parent 9c375c582d
commit 8daa6491a3
1 changed files with 219 additions and 238 deletions

View File

@ -111,281 +111,262 @@ var _ = Describe("ImageUpdateAutomation", func() {
Expect(initGitRepo(gitServer, "testdata/appconfig", branch, repositoryPath)).To(Succeed()) Expect(initGitRepo(gitServer, "testdata/appconfig", branch, repositoryPath)).To(Succeed())
}) })
// These are used for end-to-end tests; withImagePolicy is endToEnd := func(impl, proto string) func() {
// effectively parameterised on these two values. return func() {
var (
// set the proto and impl in BeforeEach
proto string
impl string
)
withImagePolicy := func() {
var (
// for cloning locally
cloneLocalRepoURL string
// for the controller
repoURL string
localRepo *git.Repository
policy *imagev1_reflect.ImagePolicy
policyKey types.NamespacedName
gitRepoKey types.NamespacedName
commitMessage string
)
const latestImage = "helloworld:1.0.1"
const evenLatestImage = "helloworld:1.2.0"
BeforeEach(func() {
cloneLocalRepoURL = gitServer.HTTPAddressWithCredentials() + repositoryPath
if proto == "http" {
repoURL = cloneLocalRepoURL // NB not testing auth for git over HTTP
} else if proto == "ssh" {
sshURL := gitServer.SSHAddress()
// this is expected to use 127.0.0.1, but host key
// checking usually wants a hostname, so use
// "localhost".
sshURL = strings.Replace(sshURL, "127.0.0.1", "localhost", 1)
repoURL = sshURL + repositoryPath
go func() {
defer GinkgoRecover()
gitServer.StartSSH()
}()
} else {
Fail("proto not set to http or ssh")
}
commitMessage = "Commit a difference " + randStringRunes(5)
Expect(initGitRepo(gitServer, "testdata/appconfig", branch, repositoryPath)).To(Succeed())
var err error
localRepo, err = git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
URL: cloneLocalRepoURL,
RemoteName: "origin",
ReferenceName: plumbing.NewBranchReferenceName(branch),
})
Expect(err).ToNot(HaveOccurred())
gitRepoKey = types.NamespacedName{
Name: "image-auto-" + randStringRunes(5),
Namespace: namespace.Name,
}
gitRepo := &sourcev1.GitRepository{
ObjectMeta: metav1.ObjectMeta{
Name: gitRepoKey.Name,
Namespace: namespace.Name,
},
Spec: sourcev1.GitRepositorySpec{
URL: repoURL,
Interval: metav1.Duration{Duration: time.Minute},
GitImplementation: impl,
},
}
// If using SSH, we need to provide an identity (private
// key) and known_hosts file in a secret.
if proto == "ssh" {
url, err := url.Parse(repoURL)
Expect(err).ToNot(HaveOccurred())
knownhosts, err := ssh.ScanHostKey(url.Host, 5*time.Second)
Expect(err).ToNot(HaveOccurred())
keygen := ssh.NewRSAGenerator(2048)
pair, err := keygen.Generate()
Expect(err).ToNot(HaveOccurred())
sec := &corev1.Secret{
StringData: map[string]string{
"known_hosts": string(knownhosts),
"identity": string(pair.PrivateKey),
"identity.pub": string(pair.PublicKey),
},
}
sec.Name = "git-secret-" + randStringRunes(5)
sec.Namespace = namespace.Name
Expect(k8sClient.Create(context.Background(), sec)).To(Succeed())
gitRepo.Spec.SecretRef = &meta.LocalObjectReference{Name: sec.Name}
}
Expect(k8sClient.Create(context.Background(), gitRepo)).To(Succeed())
policyKey = types.NamespacedName{
Name: "policy-" + randStringRunes(5),
Namespace: namespace.Name,
}
// NB not testing the image reflector controller; this
// will make a "fully formed" ImagePolicy object.
policy = &imagev1_reflect.ImagePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: policyKey.Name,
Namespace: policyKey.Namespace,
},
Spec: imagev1_reflect.ImagePolicySpec{
ImageRepositoryRef: meta.LocalObjectReference{
Name: "not-expected-to-exist",
},
Policy: imagev1_reflect.ImagePolicyChoice{
SemVer: &imagev1_reflect.SemVerPolicy{
Range: "1.x",
},
},
},
Status: imagev1_reflect.ImagePolicyStatus{
LatestImage: latestImage,
},
}
Expect(k8sClient.Create(context.Background(), policy)).To(Succeed())
Expect(k8sClient.Status().Update(context.Background(), policy)).To(Succeed())
})
AfterEach(func() {
Expect(k8sClient.Delete(context.Background(), namespace)).To(Succeed())
Expect(k8sClient.Delete(context.Background(), policy)).To(Succeed())
Expect(gitServer.StopSSH()).To(Succeed())
})
Context("with Setters", func() {
var ( var (
updateKey types.NamespacedName // for cloning locally
updateBySetters *imagev1.ImageUpdateAutomation cloneLocalRepoURL string
// for the controller
repoURL string
localRepo *git.Repository
policy *imagev1_reflect.ImagePolicy
policyKey types.NamespacedName
gitRepoKey types.NamespacedName
commitMessage string
) )
const latestImage = "helloworld:1.0.1"
const evenLatestImage = "helloworld:1.2.0"
BeforeEach(func() { BeforeEach(func() {
// Insert a setter reference into the deployment file, cloneLocalRepoURL = gitServer.HTTPAddressWithCredentials() + repositoryPath
// before creating the automation object itself. if proto == "http" {
commitInRepo(cloneLocalRepoURL, branch, "Install setter marker", func(tmp string) { repoURL = cloneLocalRepoURL // NB not testing auth for git over HTTP
replaceMarker(tmp, policyKey) } else if proto == "ssh" {
sshURL := gitServer.SSHAddress()
// this is expected to use 127.0.0.1, but host key
// checking usually wants a hostname, so use
// "localhost".
sshURL = strings.Replace(sshURL, "127.0.0.1", "localhost", 1)
repoURL = sshURL + repositoryPath
go func() {
defer GinkgoRecover()
gitServer.StartSSH()
}()
} else {
Fail("proto not set to http or ssh")
}
commitMessage = "Commit a difference " + randStringRunes(5)
Expect(initGitRepo(gitServer, "testdata/appconfig", branch, repositoryPath)).To(Succeed())
var err error
localRepo, err = git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
URL: cloneLocalRepoURL,
RemoteName: "origin",
ReferenceName: plumbing.NewBranchReferenceName(branch),
}) })
Expect(err).ToNot(HaveOccurred())
// pull the head commit we just pushed, so it's not gitRepoKey = types.NamespacedName{
// considered a new commit when checking for a commit Name: "image-auto-" + randStringRunes(5),
// made by automation. Namespace: namespace.Name,
waitForNewHead(localRepo, branch)
// now create the automation object, and let it (one
// hopes!) make a commit itself.
updateKey = types.NamespacedName{
Namespace: gitRepoKey.Namespace,
Name: "update-" + randStringRunes(5),
} }
updateBySetters = &imagev1.ImageUpdateAutomation{
gitRepo := &sourcev1.GitRepository{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: updateKey.Name, Name: gitRepoKey.Name,
Namespace: updateKey.Namespace, Namespace: namespace.Name,
}, },
Spec: imagev1.ImageUpdateAutomationSpec{ Spec: sourcev1.GitRepositorySpec{
Interval: metav1.Duration{Duration: 2 * time.Hour}, // this is to ensure any subsequent run should be outside the scope of the testing URL: repoURL,
Checkout: imagev1.GitCheckoutSpec{ Interval: metav1.Duration{Duration: time.Minute},
GitRepositoryRef: meta.LocalObjectReference{ GitImplementation: impl,
Name: gitRepoKey.Name,
},
Branch: branch,
},
Update: &imagev1.UpdateStrategy{
Strategy: imagev1.UpdateStrategySetters,
},
Commit: imagev1.CommitSpec{
MessageTemplate: commitMessage,
},
}, },
} }
Expect(k8sClient.Create(context.Background(), updateBySetters)).To(Succeed())
// wait for a new commit to be made by the controller // If using SSH, we need to provide an identity (private
waitForNewHead(localRepo, branch) // key) and known_hosts file in a secret.
if proto == "ssh" {
url, err := url.Parse(repoURL)
Expect(err).ToNot(HaveOccurred())
knownhosts, err := ssh.ScanHostKey(url.Host, 5*time.Second)
Expect(err).ToNot(HaveOccurred())
keygen := ssh.NewRSAGenerator(2048)
pair, err := keygen.Generate()
Expect(err).ToNot(HaveOccurred())
sec := &corev1.Secret{
StringData: map[string]string{
"known_hosts": string(knownhosts),
"identity": string(pair.PrivateKey),
"identity.pub": string(pair.PublicKey),
},
}
sec.Name = "git-secret-" + randStringRunes(5)
sec.Namespace = namespace.Name
Expect(k8sClient.Create(context.Background(), sec)).To(Succeed())
gitRepo.Spec.SecretRef = &meta.LocalObjectReference{Name: sec.Name}
}
Expect(k8sClient.Create(context.Background(), gitRepo)).To(Succeed())
policyKey = types.NamespacedName{
Name: "policy-" + randStringRunes(5),
Namespace: namespace.Name,
}
// NB not testing the image reflector controller; this
// will make a "fully formed" ImagePolicy object.
policy = &imagev1_reflect.ImagePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: policyKey.Name,
Namespace: policyKey.Namespace,
},
Spec: imagev1_reflect.ImagePolicySpec{
ImageRepositoryRef: meta.LocalObjectReference{
Name: "not-expected-to-exist",
},
Policy: imagev1_reflect.ImagePolicyChoice{
SemVer: &imagev1_reflect.SemVerPolicy{
Range: "1.x",
},
},
},
Status: imagev1_reflect.ImagePolicyStatus{
LatestImage: latestImage,
},
}
Expect(k8sClient.Create(context.Background(), policy)).To(Succeed())
Expect(k8sClient.Status().Update(context.Background(), policy)).To(Succeed())
}) })
AfterEach(func() { AfterEach(func() {
Expect(k8sClient.Delete(context.Background(), updateBySetters)).To(Succeed()) Expect(k8sClient.Delete(context.Background(), namespace)).To(Succeed())
Expect(k8sClient.Delete(context.Background(), policy)).To(Succeed())
Expect(gitServer.StopSSH()).To(Succeed())
}) })
It("updates to the most recent image", func() { Context("with Setters", func() {
// having passed the BeforeEach, we should see a commit
head, _ := localRepo.Head()
commit, err := localRepo.CommitObject(head.Hash())
Expect(err).ToNot(HaveOccurred())
Expect(commit.Message).To(Equal(commitMessage))
var newObj imagev1.ImageUpdateAutomation var (
Expect(k8sClient.Get(context.Background(), updateKey, &newObj)).To(Succeed()) updateKey types.NamespacedName
Expect(newObj.Status.LastPushCommit).To(Equal(head.Hash().String())) updateBySetters *imagev1.ImageUpdateAutomation
Expect(newObj.Status.LastPushTime).ToNot(BeNil()) )
compareRepoWithExpected(cloneLocalRepoURL, branch, "testdata/appconfig-setters-expected", func(tmp string) { BeforeEach(func() {
replaceMarker(tmp, policyKey) // Insert a setter reference into the deployment file,
}) // before creating the automation object itself.
}) commitInRepo(cloneLocalRepoURL, branch, "Install setter marker", func(tmp string) {
replaceMarker(tmp, policyKey)
})
It("stops updating when suspended", func() { // pull the head commit we just pushed, so it's not
// suspend it, and check that reconciliation does not run // considered a new commit when checking for a commit
var updatePatch imagev1.ImageUpdateAutomation // made by automation.
updatePatch.Name = updateKey.Name waitForNewHead(localRepo, branch)
updatePatch.Namespace = updateKey.Namespace
updatePatch.Spec.Suspend = true // now create the automation object, and let it (one
Expect(k8sClient.Patch(context.Background(), &updatePatch, client.Merge)).To(Succeed()) // hopes!) make a commit itself.
// wait for the suspension to reach the cache updateKey = types.NamespacedName{
var newUpdate imagev1.ImageUpdateAutomation Namespace: gitRepoKey.Namespace,
Eventually(func() bool { Name: "update-" + randStringRunes(5),
if err := imageAutoReconciler.Get(context.Background(), updateKey, &newUpdate); err != nil {
return false
} }
return newUpdate.Spec.Suspend updateBySetters = &imagev1.ImageUpdateAutomation{
}, timeout, time.Second).Should(BeTrue()) ObjectMeta: metav1.ObjectMeta{
// run the reconciliation explicitly, and make sure it Name: updateKey.Name,
// doesn't do anything Namespace: updateKey.Namespace,
result, err := imageAutoReconciler.Reconcile(logr.NewContext(context.TODO(), ctrl.Log), ctrl.Request{ },
NamespacedName: updateKey, Spec: imagev1.ImageUpdateAutomationSpec{
Interval: metav1.Duration{Duration: 2 * time.Hour}, // this is to ensure any subsequent run should be outside the scope of the testing
Checkout: imagev1.GitCheckoutSpec{
GitRepositoryRef: meta.LocalObjectReference{
Name: gitRepoKey.Name,
},
Branch: branch,
},
Update: &imagev1.UpdateStrategy{
Strategy: imagev1.UpdateStrategySetters,
},
Commit: imagev1.CommitSpec{
MessageTemplate: commitMessage,
},
},
}
Expect(k8sClient.Create(context.Background(), updateBySetters)).To(Succeed())
// wait for a new commit to be made by the controller
waitForNewHead(localRepo, branch)
}) })
Expect(err).To(BeNil())
// this ought to fail if suspend is not working, since the item would be requeued;
// but if not, additional checks lie below.
Expect(result).To(Equal(ctrl.Result{}))
var checkUpdate imagev1.ImageUpdateAutomation AfterEach(func() {
Expect(k8sClient.Get(context.Background(), updateKey, &checkUpdate)).To(Succeed()) Expect(k8sClient.Delete(context.Background(), updateBySetters)).To(Succeed())
Expect(checkUpdate.Status.ObservedGeneration).NotTo(Equal(checkUpdate.ObjectMeta.Generation)) })
})
It("runs when the reconcile request annotation is added", func() { It("updates to the most recent image", func() {
// the automation has run, and is not expected to run // having passed the BeforeEach, we should see a commit
// again for 2 hours. Make a commit to the git repo head, _ := localRepo.Head()
// which needs to be undone by automation, then add commit, err := localRepo.CommitObject(head.Hash())
// the annotation and make sure it runs again. Expect(err).ToNot(HaveOccurred())
Expect(k8sClient.Get(context.Background(), updateKey, updateBySetters)).To(Succeed()) Expect(commit.Message).To(Equal(commitMessage))
Expect(updateBySetters.Status.LastAutomationRunTime).ToNot(BeNil())
var newObj imagev1.ImageUpdateAutomation
Expect(k8sClient.Get(context.Background(), updateKey, &newObj)).To(Succeed())
Expect(newObj.Status.LastPushCommit).To(Equal(head.Hash().String()))
Expect(newObj.Status.LastPushTime).ToNot(BeNil())
compareRepoWithExpected(cloneLocalRepoURL, branch, "testdata/appconfig-setters-expected", func(tmp string) {
replaceMarker(tmp, policyKey)
})
})
It("stops updating when suspended", func() {
// suspend it, and check that reconciliation does not run
var updatePatch imagev1.ImageUpdateAutomation
updatePatch.Name = updateKey.Name
updatePatch.Namespace = updateKey.Namespace
updatePatch.Spec.Suspend = true
Expect(k8sClient.Patch(context.Background(), &updatePatch, client.Merge)).To(Succeed())
// wait for the suspension to reach the cache
var newUpdate imagev1.ImageUpdateAutomation
Eventually(func() bool {
if err := imageAutoReconciler.Get(context.Background(), updateKey, &newUpdate); err != nil {
return false
}
return newUpdate.Spec.Suspend
}, timeout, time.Second).Should(BeTrue())
// run the reconciliation explicitly, and make sure it
// doesn't do anything
result, err := imageAutoReconciler.Reconcile(logr.NewContext(context.TODO(), ctrl.Log), ctrl.Request{
NamespacedName: updateKey,
})
Expect(err).To(BeNil())
// this ought to fail if suspend is not working, since the item would be requeued;
// but if not, additional checks lie below.
Expect(result).To(Equal(ctrl.Result{}))
var checkUpdate imagev1.ImageUpdateAutomation
Expect(k8sClient.Get(context.Background(), updateKey, &checkUpdate)).To(Succeed())
Expect(checkUpdate.Status.ObservedGeneration).NotTo(Equal(checkUpdate.ObjectMeta.Generation))
})
It("runs when the reconcile request annotation is added", func() {
// the automation has run, and is not expected to run
// again for 2 hours. Make a commit to the git repo
// which needs to be undone by automation, then add
// the annotation and make sure it runs again.
Expect(k8sClient.Get(context.Background(), updateKey, updateBySetters)).To(Succeed())
Expect(updateBySetters.Status.LastAutomationRunTime).ToNot(BeNil())
})
}) })
}) }
} }
Context("Using go-git", func() { Context("Using go-git", func() {
BeforeEach(func() { impl = sourcev1.GoGitImplementation })
Context("with HTTP", func() { Context("with HTTP", func() {
BeforeEach(func() { proto = "http" }) Describe("runs end to end", endToEnd(sourcev1.GoGitImplementation, "http"))
Describe("with image policy", withImagePolicy)
}) })
Context("with SSH", func() { Context("with SSH", func() {
BeforeEach(func() { proto = "ssh" }) Describe("runs end to end", endToEnd(sourcev1.GoGitImplementation, "ssh"))
Describe("with image policy", withImagePolicy)
}) })
}) })
Context("Using libgit2", func() { Context("Using libgit2", func() {
BeforeEach(func() { impl = sourcev1.LibGit2Implementation })
Context("with HTTP", func() { Context("with HTTP", func() {
BeforeEach(func() { proto = "http" }) Describe("runs end to end", endToEnd(sourcev1.LibGit2Implementation, "http"))
Describe("with image policy", withImagePolicy)
}) })
// Marked "Pending" because the libgit2 SSH implementation
// won't work with the gittestserver yet -- see
// https://github.com/fluxcd/source-controller/issues/287
Context("with SSH", func() { Context("with SSH", func() {
BeforeEach(func() { proto = "ssh" }) Describe("runs end to end", endToEnd(sourcev1.LibGit2Implementation, "ssh"))
Describe("with image policy", withImagePolicy)
}) })
}) })