1152 lines
37 KiB
Go
1152 lines
37 KiB
Go
/*
|
|
Copyright 2020 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 controllers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp"
|
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
|
"github.com/go-git/go-billy/v5/memfs"
|
|
"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/object"
|
|
"github.com/go-git/go-git/v5/storage/memory"
|
|
"github.com/go-logr/logr"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/otiai10/copy"
|
|
corev1 "k8s.io/api/core/v1"
|
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/kubernetes/scheme"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
|
"github.com/fluxcd/pkg/apis/meta"
|
|
"github.com/fluxcd/pkg/gittestserver"
|
|
"github.com/fluxcd/pkg/ssh"
|
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
|
|
|
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
|
"github.com/fluxcd/image-automation-controller/pkg/test"
|
|
"github.com/fluxcd/image-automation-controller/pkg/update"
|
|
)
|
|
|
|
// Copied from
|
|
// https://github.com/fluxcd/source-controller/blob/master/controllers/suite_test.go
|
|
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz1234567890")
|
|
|
|
func randStringRunes(n int) string {
|
|
b := make([]rune, n)
|
|
for i := range b {
|
|
b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
const (
|
|
timeout = 10 * time.Second
|
|
testAuthorName = "Flux B Ot"
|
|
testAuthorEmail = "fluxbot@example.com"
|
|
testCommitTemplate = `Commit summary
|
|
|
|
Automation: {{ .AutomationObject }}
|
|
|
|
Files:
|
|
{{ range $filename, $_ := .Updated.Files -}}
|
|
- {{ $filename }}
|
|
{{ end -}}
|
|
|
|
Objects:
|
|
{{ range $resource, $_ := .Updated.Objects -}}
|
|
{{ if eq $resource.Kind "Deployment" -}}
|
|
- {{ $resource.Kind | lower }} {{ $resource.Name | lower }}
|
|
{{ else -}}
|
|
- {{ $resource.Kind }} {{ $resource.Name }}
|
|
{{ end -}}
|
|
{{ end -}}
|
|
|
|
Images:
|
|
{{ range .Updated.Images -}}
|
|
- {{.}} ({{.Policy.Name}})
|
|
{{ end -}}
|
|
`
|
|
testCommitMessageFmt = `Commit summary
|
|
|
|
Automation: %s/update-test
|
|
|
|
Files:
|
|
- deploy.yaml
|
|
Objects:
|
|
- deployment test
|
|
Images:
|
|
- helloworld:v1.0.0 (%s)
|
|
`
|
|
)
|
|
|
|
func TestImageUpdateAutomation_commit_message(t *testing.T) {
|
|
policySpec := imagev1_reflect.ImagePolicySpec{
|
|
ImageRepositoryRef: meta.NamespacedObjectReference{
|
|
Name: "not-expected-to-exist",
|
|
},
|
|
Policy: imagev1_reflect.ImagePolicyChoice{
|
|
SemVer: &imagev1_reflect.SemVerPolicy{
|
|
Range: "1.x",
|
|
},
|
|
},
|
|
}
|
|
fixture := "testdata/appconfig"
|
|
latest := "helloworld:v1.0.0"
|
|
|
|
testWithRepoAndImagePolicy(
|
|
NewWithT(t), fixture, policySpec, latest,
|
|
func(g *WithT, s repoAndPolicyArgs, repoURL string, localRepo *git.Repository) {
|
|
commitMessage := fmt.Sprintf(testCommitMessageFmt, s.namespace, s.imagePolicyName)
|
|
|
|
// Update the setter marker in the repo.
|
|
policyKey := types.NamespacedName{
|
|
Name: s.imagePolicyName,
|
|
Namespace: s.namespace,
|
|
}
|
|
commitInRepo(g, repoURL, s.branch, "Install setter marker", func(tmp string) {
|
|
g.Expect(replaceMarker(tmp, policyKey)).To(Succeed())
|
|
})
|
|
|
|
// Pull the head commit that was just pushed, so it's not considered a new
|
|
// commit when checking for a commit made by automation.
|
|
waitForNewHead(g, localRepo, s.branch)
|
|
|
|
// Create the automation object and let it make a commit itself.
|
|
updateStrategy := &imagev1.UpdateStrategy{
|
|
Strategy: imagev1.UpdateStrategySetters,
|
|
}
|
|
err := createImageUpdateAutomation("update-test", s.namespace, s.gitRepoName, s.branch, "", testCommitTemplate, "", updateStrategy)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Wait for a new commit to be made by the controller.
|
|
waitForNewHead(g, localRepo, s.branch)
|
|
|
|
head, _ := localRepo.Head()
|
|
commit, err := localRepo.CommitObject(head.Hash())
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(commit.Author).NotTo(BeNil())
|
|
g.Expect(commit.Author.Name).To(Equal(testAuthorName))
|
|
g.Expect(commit.Author.Email).To(Equal(testAuthorEmail))
|
|
g.Expect(commit.Message).To(Equal(commitMessage))
|
|
})
|
|
}
|
|
|
|
func TestImageUpdateAutomation_update_path(t *testing.T) {
|
|
policySpec := imagev1_reflect.ImagePolicySpec{
|
|
ImageRepositoryRef: meta.NamespacedObjectReference{
|
|
Name: "not-expected-to-exist",
|
|
},
|
|
Policy: imagev1_reflect.ImagePolicyChoice{
|
|
SemVer: &imagev1_reflect.SemVerPolicy{
|
|
Range: "1.x",
|
|
},
|
|
},
|
|
}
|
|
fixture := "testdata/pathconfig"
|
|
latest := "helloworld:v1.0.0"
|
|
|
|
testWithRepoAndImagePolicy(
|
|
NewWithT(t), fixture, policySpec, latest,
|
|
func(g *WithT, s repoAndPolicyArgs, repoURL string, localRepo *git.Repository) {
|
|
// Update the setter marker in the repo.
|
|
policyKey := types.NamespacedName{
|
|
Name: s.imagePolicyName,
|
|
Namespace: s.namespace,
|
|
}
|
|
commitInRepo(g, repoURL, s.branch, "Install setter marker", func(tmp string) {
|
|
g.Expect(replaceMarker(path.Join(tmp, "yes"), policyKey)).To(Succeed())
|
|
})
|
|
commitInRepo(g, repoURL, s.branch, "Install setter marker", func(tmp string) {
|
|
g.Expect(replaceMarker(path.Join(tmp, "no"), policyKey)).To(Succeed())
|
|
})
|
|
|
|
// Pull the head commit that was just pushed, so it's not considered a new
|
|
// commit when checking for a commit made by automation.
|
|
waitForNewHead(g, localRepo, s.branch)
|
|
|
|
// Create the automation object and let it make a commit itself.
|
|
updateStrategy := &imagev1.UpdateStrategy{
|
|
Strategy: imagev1.UpdateStrategySetters,
|
|
Path: "./yes",
|
|
}
|
|
err := createImageUpdateAutomation("update-test", s.namespace, s.gitRepoName, s.branch, "", testCommitTemplate, "", updateStrategy)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Wait for a new commit to be made by the controller.
|
|
waitForNewHead(g, localRepo, s.branch)
|
|
|
|
head, _ := localRepo.Head()
|
|
commit, err := localRepo.CommitObject(head.Hash())
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(commit.Message).ToNot(ContainSubstring("update-no"))
|
|
g.Expect(commit.Message).To(ContainSubstring("update-yes"))
|
|
})
|
|
}
|
|
|
|
func TestImageUpdateAutomation_signed_commit(t *testing.T) {
|
|
policySpec := imagev1_reflect.ImagePolicySpec{
|
|
ImageRepositoryRef: meta.NamespacedObjectReference{
|
|
Name: "not-expected-to-exist",
|
|
},
|
|
Policy: imagev1_reflect.ImagePolicyChoice{
|
|
SemVer: &imagev1_reflect.SemVerPolicy{
|
|
Range: "1.x",
|
|
},
|
|
},
|
|
}
|
|
fixture := "testdata/appconfig"
|
|
latest := "helloworld:v1.0.0"
|
|
|
|
testWithRepoAndImagePolicy(
|
|
NewWithT(t), fixture, policySpec, latest,
|
|
func(g *WithT, s repoAndPolicyArgs, repoURL string, localRepo *git.Repository) {
|
|
signingKeySecretName := "signing-key-secret-" + randStringRunes(5)
|
|
// Update the setter marker in the repo.
|
|
policyKey := types.NamespacedName{
|
|
Name: s.imagePolicyName,
|
|
Namespace: s.namespace,
|
|
}
|
|
commitInRepo(g, repoURL, s.branch, "Install setter marker", func(tmp string) {
|
|
g.Expect(replaceMarker(tmp, policyKey)).To(Succeed())
|
|
})
|
|
|
|
// Pull the head commit that was just pushed, so it's not considered a new
|
|
// commit when checking for a commit made by automation.
|
|
waitForNewHead(g, localRepo, s.branch)
|
|
|
|
pgpEntity, err := createSigningKeyPair(signingKeySecretName, s.namespace)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create signing key pair")
|
|
|
|
// Create the automation object and let it make a commit itself.
|
|
updateStrategy := &imagev1.UpdateStrategy{
|
|
Strategy: imagev1.UpdateStrategySetters,
|
|
}
|
|
err = createImageUpdateAutomation("update-test", s.namespace, s.gitRepoName, s.branch, "", testCommitTemplate, signingKeySecretName, updateStrategy)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Wait for a new commit to be made by the controller.
|
|
waitForNewHead(g, localRepo, s.branch)
|
|
|
|
head, err := localRepo.Head()
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
commit, err := localRepo.CommitObject(head.Hash())
|
|
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 public key.
|
|
err = pgpEntity.Serialize(w)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
err = w.Close()
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Verify commit.
|
|
ent, err := commit.Verify(b.String())
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(ent.PrimaryKey.Fingerprint).To(Equal(pgpEntity.PrimaryKey.Fingerprint))
|
|
})
|
|
}
|
|
|
|
func TestImageUpdateAutomation_e2e(t *testing.T) {
|
|
gitImpls := []string{sourcev1.GoGitImplementation, sourcev1.LibGit2Implementation}
|
|
protos := []string{"http", "ssh"}
|
|
|
|
testFunc := func(t *testing.T, proto string, impl string) {
|
|
g := NewWithT(t)
|
|
|
|
const latestImage = "helloworld:1.0.1"
|
|
|
|
namespace := "image-auto-test-" + randStringRunes(5)
|
|
branch := randStringRunes(8)
|
|
repositoryPath := "/config-" + randStringRunes(6) + ".git"
|
|
gitRepoName := "image-auto-" + randStringRunes(5)
|
|
gitSecretName := "git-secret-" + randStringRunes(5)
|
|
imagePolicyName := "policy-" + randStringRunes(5)
|
|
updateStrategy := &imagev1.UpdateStrategy{
|
|
Strategy: imagev1.UpdateStrategySetters,
|
|
}
|
|
|
|
// Create a test namespace.
|
|
nsCleanup, err := createNamespace(namespace)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create test namespace")
|
|
defer func() {
|
|
g.Expect(nsCleanup()).To(Succeed())
|
|
}()
|
|
|
|
// Create git server.
|
|
gitServer, err := setupGitTestServer()
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create test git server")
|
|
defer os.RemoveAll(gitServer.Root())
|
|
defer gitServer.StopHTTP()
|
|
|
|
cloneLocalRepoURL := gitServer.HTTPAddressWithCredentials() + repositoryPath
|
|
repoURL, err := getRepoURL(gitServer, repositoryPath, proto)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Start the ssh server if needed.
|
|
if proto == "ssh" {
|
|
// NOTE: Check how this is done in source-controller.
|
|
go func() {
|
|
gitServer.StartSSH()
|
|
}()
|
|
defer func() {
|
|
g.Expect(gitServer.StopSSH()).To(Succeed())
|
|
}()
|
|
}
|
|
|
|
commitMessage := "Commit a difference " + randStringRunes(5)
|
|
|
|
// Initialize a git repo.
|
|
g.Expect(initGitRepo(gitServer, "testdata/appconfig", branch, repositoryPath)).To(Succeed())
|
|
|
|
// Clone the repo locally.
|
|
localRepo, err := cloneRepo(cloneLocalRepoURL, branch)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to clone git repo")
|
|
|
|
// Create GitRepository resource for the above repo.
|
|
if proto == "ssh" {
|
|
// SSH requires an identity (private key) and known_hosts file
|
|
// in a secret.
|
|
err = createSSHIdentitySecret(gitSecretName, namespace, repoURL)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
err = createGitRepository(gitRepoName, namespace, impl, repoURL, gitSecretName)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
} else {
|
|
err = createGitRepository(gitRepoName, namespace, impl, repoURL, "")
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
|
|
// Create an image policy.
|
|
policyKey := types.NamespacedName{
|
|
Name: imagePolicyName,
|
|
Namespace: namespace,
|
|
}
|
|
|
|
// Create ImagePolicy and ImageUpdateAutomation resource for each of the
|
|
// test cases and cleanup at the end.
|
|
|
|
t.Run("PushSpec", func(t *testing.T) {
|
|
// NB not testing the image reflector controller; this
|
|
// will make a "fully formed" ImagePolicy object.
|
|
err = createImagePolicyWithLatestImage(imagePolicyName, namespace, "not-expected-to-exist", "1.x", latestImage)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create ImagePolicy resource")
|
|
|
|
defer func() {
|
|
g.Expect(deleteImagePolicy(imagePolicyName, namespace)).ToNot(HaveOccurred())
|
|
}()
|
|
|
|
imageUpdateAutomationName := "update-" + randStringRunes(5)
|
|
pushBranch := "pr-" + randStringRunes(5)
|
|
|
|
t.Run("update with PushSpec", func(t *testing.T) {
|
|
commitInRepo(g, cloneLocalRepoURL, branch, "Install setter marker", func(tmp string) {
|
|
g.Expect(replaceMarker(tmp, policyKey)).To(Succeed())
|
|
})
|
|
// Pull the head commit we just pushed, so it's not
|
|
// considered a new commit when checking for a commit
|
|
// made by automation.
|
|
waitForNewHead(g, localRepo, branch)
|
|
|
|
// Now create the automation object, and let it (one
|
|
// hopes!) make a commit itself.
|
|
err = createImageUpdateAutomation(imageUpdateAutomationName, namespace, gitRepoName, branch, pushBranch, commitMessage, "", updateStrategy)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Wait for a new commit to be made by the controller.
|
|
waitForNewHead(g, localRepo, pushBranch)
|
|
|
|
head, err := localRepo.Reference(plumbing.NewRemoteReferenceName(originRemote, pushBranch), true)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
commit, err := localRepo.CommitObject(head.Hash())
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(commit.Message).To(Equal(commitMessage))
|
|
})
|
|
|
|
t.Run("push branch gets updated", func(t *testing.T) {
|
|
// Get the head hash before update.
|
|
head, err := localRepo.Reference(plumbing.NewRemoteReferenceName(originRemote, pushBranch), true)
|
|
headHash := head.String()
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Update the policy and expect another commit in the push
|
|
// branch.
|
|
err = updateImagePolicyWithLatestImage(imagePolicyName, namespace, "helloworld:v1.3.0")
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
waitForNewHead(g, localRepo, pushBranch)
|
|
head, err = localRepo.Reference(plumbing.NewRemoteReferenceName(originRemote, pushBranch), true)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(head.String()).NotTo(Equal(headHash))
|
|
})
|
|
|
|
t.Run("still pushes to the push branch after it's merged", func(t *testing.T) {
|
|
// Get the head hash before.
|
|
head, err := localRepo.Reference(plumbing.NewRemoteReferenceName(originRemote, pushBranch), true)
|
|
headHash := head.String()
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Merge the push branch into checkout branch, and push the merge commit
|
|
// upstream.
|
|
// waitForNewHead() leaves the repo at the head of the branch given, i.e., the
|
|
// push branch), so we have to check out the "main" branch first.
|
|
g.Expect(checkoutBranch(localRepo, branch)).To(Succeed())
|
|
mergeBranchIntoHead(g, localRepo, pushBranch)
|
|
|
|
// Update the policy and expect another commit in the push
|
|
// branch.
|
|
err = updateImagePolicyWithLatestImage(imagePolicyName, namespace, "helloworld:v1.3.1")
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
waitForNewHead(g, localRepo, pushBranch)
|
|
|
|
head, err = localRepo.Reference(plumbing.NewRemoteReferenceName(originRemote, pushBranch), true)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
g.Expect(head.String()).NotTo(Equal(headHash))
|
|
})
|
|
|
|
// Cleanup the image update automation used above.
|
|
g.Expect(deleteImageUpdateAutomation(imageUpdateAutomationName, namespace)).To(Succeed())
|
|
})
|
|
|
|
t.Run("with update strategy setters", func(t *testing.T) {
|
|
err = createImagePolicyWithLatestImage(imagePolicyName, namespace, "not-expected-to-exist", "1.x", latestImage)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create ImagePolicy resource")
|
|
|
|
defer func() {
|
|
g.Expect(deleteImagePolicy(imagePolicyName, namespace)).ToNot(HaveOccurred())
|
|
}()
|
|
|
|
// Insert a setter reference into the deployment file,
|
|
// before creating the automation object itself.
|
|
commitInRepo(g, cloneLocalRepoURL, branch, "Install setter marker", func(tmp string) {
|
|
g.Expect(replaceMarker(tmp, policyKey)).To(Succeed())
|
|
})
|
|
|
|
// Pull the head commit we just pushed, so it's not
|
|
// considered a new commit when checking for a commit
|
|
// made by automation.
|
|
waitForNewHead(g, localRepo, branch)
|
|
|
|
// Now create the automation object, and let it (one
|
|
// hopes!) make a commit itself.
|
|
updateKey := types.NamespacedName{
|
|
Namespace: namespace,
|
|
Name: "update-" + randStringRunes(5),
|
|
}
|
|
err = createImageUpdateAutomation(updateKey.Name, namespace, gitRepoName, branch, "", commitMessage, "", updateStrategy)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
defer func() {
|
|
g.Expect(deleteImageUpdateAutomation(updateKey.Name, namespace)).To(Succeed())
|
|
}()
|
|
|
|
// Wait for a new commit to be made by the controller.
|
|
waitForNewHead(g, localRepo, branch)
|
|
|
|
// Check if the repo head matches with the ImageUpdateAutomation
|
|
// last push commit status.
|
|
head, err := localRepo.Head()
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
var newObj imagev1.ImageUpdateAutomation
|
|
g.Expect(testEnv.Get(context.Background(), updateKey, &newObj)).To(Succeed())
|
|
g.Expect(newObj.Status.LastPushCommit).To(Equal(head.Hash().String()))
|
|
g.Expect(newObj.Status.LastPushTime).ToNot(BeNil())
|
|
|
|
compareRepoWithExpected(g, cloneLocalRepoURL, branch, "testdata/appconfig-setters-expected", func(tmp string) {
|
|
g.Expect(replaceMarker(tmp, policyKey)).To(Succeed())
|
|
})
|
|
})
|
|
|
|
t.Run("no reconciliation when object is suspended", func(t *testing.T) {
|
|
err = createImagePolicyWithLatestImage(imagePolicyName, namespace, "not-expected-to-exist", "1.x", latestImage)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create ImagePolicy resource")
|
|
|
|
defer func() {
|
|
g.Expect(deleteImagePolicy(imagePolicyName, namespace)).ToNot(HaveOccurred())
|
|
}()
|
|
|
|
// Create the automation object.
|
|
updateKey := types.NamespacedName{
|
|
Namespace: namespace,
|
|
Name: "update-" + randStringRunes(5),
|
|
}
|
|
err = createImageUpdateAutomation(updateKey.Name, namespace, gitRepoName, branch, "", commitMessage, "", updateStrategy)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
defer func() {
|
|
g.Expect(deleteImageUpdateAutomation(updateKey.Name, namespace)).To(Succeed())
|
|
}()
|
|
|
|
// Wait for the object to be available in the cache before
|
|
// attempting update.
|
|
g.Eventually(func() bool {
|
|
obj := &imagev1.ImageUpdateAutomation{}
|
|
if err := testEnv.Get(context.Background(), updateKey, obj); err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}, timeout, time.Second).Should(BeTrue())
|
|
|
|
// Suspend the automation object.
|
|
var updatePatch imagev1.ImageUpdateAutomation
|
|
g.Expect(testEnv.Get(context.TODO(), updateKey, &updatePatch)).To(Succeed())
|
|
updatePatch.Spec.Suspend = true
|
|
g.Expect(testEnv.Patch(context.Background(), &updatePatch, client.Merge)).To(Succeed())
|
|
|
|
// Create a new image automation reconciler and run it
|
|
// explicitly.
|
|
imageAutoReconciler := &ImageUpdateAutomationReconciler{
|
|
Client: testEnv,
|
|
Scheme: scheme.Scheme,
|
|
}
|
|
|
|
// Wait for the suspension to reach the cache
|
|
var newUpdate imagev1.ImageUpdateAutomation
|
|
g.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,
|
|
})
|
|
g.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.
|
|
g.Expect(result).To(Equal(ctrl.Result{}))
|
|
|
|
var checkUpdate imagev1.ImageUpdateAutomation
|
|
g.Expect(testEnv.Get(context.Background(), updateKey, &checkUpdate)).To(Succeed())
|
|
g.Expect(checkUpdate.Status.ObservedGeneration).NotTo(Equal(checkUpdate.ObjectMeta.Generation))
|
|
})
|
|
|
|
t.Run("reconciles with reconcile request annotation", func(t *testing.T) {
|
|
// 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.
|
|
|
|
// TODO: Implement adding request annotation.
|
|
// Refer: https://github.com/fluxcd/image-automation-controller/pull/82/commits/4fde199362b42fa37068f2e6c6885cfea474a3d1#diff-1168fadffa18bd096582ae7f8b6db744fd896bd5600ee1d1ac6ac4474af251b9L292-L334
|
|
})
|
|
}
|
|
|
|
// Run the protocol based e2e tests against the git implementations.
|
|
for _, gitImpl := range gitImpls {
|
|
for _, proto := range protos {
|
|
t.Run(fmt.Sprintf("%s_%s", gitImpl, proto), func(t *testing.T) {
|
|
testFunc(t, proto, gitImpl)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestImageUpdateAutomation_defaulting(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
branch := randStringRunes(8)
|
|
namespace := &corev1.Namespace{}
|
|
namespace.Name = "image-auto-test-" + randStringRunes(5)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
|
|
defer cancel()
|
|
|
|
// Create a test namespace.
|
|
g.Expect(testEnv.Create(ctx, namespace)).To(Succeed())
|
|
defer func() {
|
|
g.Expect(testEnv.Delete(ctx, namespace)).To(Succeed())
|
|
}()
|
|
|
|
// Create an instance of ImageUpdateAutomation.
|
|
key := types.NamespacedName{
|
|
Name: "update-" + randStringRunes(5),
|
|
Namespace: namespace.Name,
|
|
}
|
|
auto := &imagev1.ImageUpdateAutomation{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: key.Name,
|
|
Namespace: key.Namespace,
|
|
},
|
|
Spec: imagev1.ImageUpdateAutomationSpec{
|
|
SourceRef: imagev1.SourceReference{
|
|
Kind: "GitRepository",
|
|
Name: "garbage",
|
|
},
|
|
Interval: metav1.Duration{Duration: 2 * time.Hour}, // this is to ensure any subsequent run should be outside the scope of the testing
|
|
GitSpec: &imagev1.GitSpec{
|
|
Checkout: &imagev1.GitCheckoutSpec{
|
|
Reference: sourcev1.GitRepositoryRef{
|
|
Branch: branch,
|
|
},
|
|
},
|
|
// leave Update field out
|
|
Commit: imagev1.CommitSpec{
|
|
Author: imagev1.CommitUser{
|
|
Email: "fluxbot@example.com",
|
|
},
|
|
MessageTemplate: "nothing",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
g.Expect(testEnv.Create(ctx, auto)).To(Succeed())
|
|
defer func() {
|
|
g.Expect(testEnv.Delete(ctx, auto)).To(Succeed())
|
|
}()
|
|
|
|
// Should default .spec.update to {strategy: Setters}.
|
|
var fetchedAuto imagev1.ImageUpdateAutomation
|
|
g.Eventually(func() bool {
|
|
err := testEnv.Get(ctx, key, &fetchedAuto)
|
|
return err == nil
|
|
}, timeout, time.Second).Should(BeTrue())
|
|
g.Expect(fetchedAuto.Spec.Update).
|
|
To(Equal(&imagev1.UpdateStrategy{Strategy: imagev1.UpdateStrategySetters}))
|
|
}
|
|
|
|
func expectCommittedAndPushed(conditions []metav1.Condition) {
|
|
rc := apimeta.FindStatusCondition(conditions, meta.ReadyCondition)
|
|
Expect(rc).ToNot(BeNil())
|
|
Expect(rc.Message).To(ContainSubstring("committed and pushed"))
|
|
}
|
|
|
|
func replaceMarker(path string, policyKey types.NamespacedName) error {
|
|
// NB this requires knowledge of what's in the git repo, so a little brittle
|
|
deployment := filepath.Join(path, "deploy.yaml")
|
|
filebytes, err := os.ReadFile(deployment)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newfilebytes := bytes.ReplaceAll(filebytes, []byte("SETTER_SITE"), []byte(setterRef(policyKey)))
|
|
if err = os.WriteFile(deployment, 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)
|
|
}
|
|
|
|
// waitForHead fetches the remote branch given until it differs from
|
|
// the remote ref locally (or if there's no ref locally, until it has
|
|
// fetched the remote branch). It resets the working tree head to the
|
|
// remote branch ref.
|
|
func waitForNewHead(g *WithT, repo *git.Repository, branch string) {
|
|
working, err := repo.Worktree()
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Try to find the remote branch in the repo locally; this will
|
|
// fail if we're on a branch that didn't exist when we cloned the
|
|
// repo (e.g., if the automation is pushing to another branch).
|
|
remoteHeadHash := ""
|
|
remoteBranch := plumbing.NewRemoteReferenceName(originRemote, branch)
|
|
remoteHead, err := repo.Reference(remoteBranch, false)
|
|
if err != plumbing.ErrReferenceNotFound {
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
if err == nil {
|
|
remoteHeadHash = remoteHead.Hash().String()
|
|
} // otherwise, any reference fetched will do.
|
|
|
|
// Now try to fetch new commits from that remote branch
|
|
g.Eventually(func() bool {
|
|
if err := repo.Fetch(&git.FetchOptions{
|
|
RefSpecs: []config.RefSpec{
|
|
config.RefSpec("refs/heads/" + branch + ":refs/remotes/origin/" + branch),
|
|
},
|
|
}); err != nil {
|
|
return false
|
|
}
|
|
remoteHead, err = repo.Reference(remoteBranch, false)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return remoteHead.Hash().String() != remoteHeadHash
|
|
}, timeout, time.Second).Should(BeTrue())
|
|
|
|
// 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(working.Reset(&git.ResetOptions{
|
|
Commit: remoteHead.Hash(),
|
|
Mode: git.HardReset,
|
|
})).To(Succeed())
|
|
}
|
|
|
|
func compareRepoWithExpected(g *WithT, repoURL, branch, fixture string, changeFixture func(tmp string)) {
|
|
expected, err := os.MkdirTemp("", "gotest-imageauto-expected")
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
defer os.RemoveAll(expected)
|
|
copy.Copy(fixture, expected)
|
|
changeFixture(expected)
|
|
|
|
tmp, err := os.MkdirTemp("", "gotest-imageauto")
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
defer os.RemoveAll(tmp)
|
|
_, err = git.PlainClone(tmp, false, &git.CloneOptions{
|
|
URL: repoURL,
|
|
ReferenceName: plumbing.NewBranchReferenceName(branch),
|
|
})
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
test.ExpectMatchingDirectories(g, tmp, expected)
|
|
}
|
|
|
|
func commitInRepo(g *WithT, repoURL, branch, msg string, changeFiles func(path string)) {
|
|
tmp, err := os.MkdirTemp("", "gotest-imageauto")
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
defer os.RemoveAll(tmp)
|
|
repo, err := git.PlainClone(tmp, false, &git.CloneOptions{
|
|
URL: repoURL,
|
|
ReferenceName: plumbing.NewBranchReferenceName(branch),
|
|
})
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
|
|
changeFiles(tmp)
|
|
|
|
worktree, err := repo.Worktree()
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
_, err = worktree.Add(".")
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
_, err = worktree.Commit(msg, &git.CommitOptions{
|
|
Author: &object.Signature{
|
|
Name: "Testbot",
|
|
Email: "test@example.com",
|
|
When: time.Now(),
|
|
},
|
|
})
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
g.Expect(repo.Push(&git.PushOptions{RemoteName: "origin"})).To(Succeed())
|
|
}
|
|
|
|
// Initialise a git server with a repo including the files in dir.
|
|
func initGitRepo(gitServer *gittestserver.GitServer, fixture, branch, repositoryPath string) error {
|
|
fs := memfs.New()
|
|
repo, err := git.Init(memory.NewStorage(), fs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = populateRepoFromFixture(repo, fixture)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
working, err := repo.Worktree()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = working.Checkout(&git.CheckoutOptions{
|
|
Branch: plumbing.NewBranchReferenceName(branch),
|
|
Create: true,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
remote, err := repo.CreateRemote(&config.RemoteConfig{
|
|
Name: "origin",
|
|
URLs: []string{gitServer.HTTPAddressWithCredentials() + repositoryPath},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return remote.Push(&git.PushOptions{
|
|
RefSpecs: []config.RefSpec{
|
|
config.RefSpec(fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)),
|
|
},
|
|
})
|
|
}
|
|
|
|
func checkoutBranch(repo *git.Repository, branch string) error {
|
|
working, err := repo.Worktree()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// check that there's no local changes, as a sanity check
|
|
status, err := working.Status()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(status) > 0 {
|
|
for path := range status {
|
|
println(path, "is changed")
|
|
}
|
|
} // the checkout next will fail if there are changed files
|
|
|
|
if err = working.Checkout(&git.CheckoutOptions{
|
|
Branch: plumbing.NewBranchReferenceName(branch),
|
|
Create: false,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// This merges the push branch into HEAD, and pushes upstream. This is
|
|
// to simulate e.g., a PR being merged.
|
|
func mergeBranchIntoHead(g *WithT, repo *git.Repository, pushBranch string) {
|
|
// hash of head
|
|
headRef, err := repo.Head()
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
pushBranchRef, err := repo.Reference(plumbing.NewRemoteReferenceName(originRemote, pushBranch), false)
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
|
|
// You need the worktree to be able to create a commit
|
|
worktree, err := repo.Worktree()
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
_, err = worktree.Commit(fmt.Sprintf("Merge %s", pushBranch), &git.CommitOptions{
|
|
Author: &object.Signature{
|
|
Name: "Testbot",
|
|
Email: "test@example.com",
|
|
When: time.Now(),
|
|
},
|
|
Parents: []plumbing.Hash{headRef.Hash(), pushBranchRef.Hash()},
|
|
})
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
|
|
// push upstream
|
|
err = repo.Push(&git.PushOptions{
|
|
RemoteName: originRemote,
|
|
})
|
|
g.Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
type repoAndPolicyArgs struct {
|
|
namespace, gitRepoName, branch, imagePolicyName string
|
|
}
|
|
|
|
func newRepoAndPolicyArgs() repoAndPolicyArgs {
|
|
return repoAndPolicyArgs{
|
|
namespace: "image-auto-test-" + randStringRunes(5),
|
|
gitRepoName: "image-auto-test-" + randStringRunes(5),
|
|
branch: randStringRunes(8),
|
|
imagePolicyName: "policy-" + randStringRunes(5),
|
|
}
|
|
}
|
|
|
|
// testWithRepoAndImagePolicyTestFunc is the test closure function type passed
|
|
// to testWithRepoAndImagePolicy.
|
|
type testWithRepoAndImagePolicyTestFunc func(g *WithT, s repoAndPolicyArgs, repoURL string, localRepo *git.Repository)
|
|
|
|
// testWithRepoAndImagePolicy sets up a git server, a repository in the git
|
|
// server, a GitRepository object for the created git repo, and an ImagePolicy
|
|
// with the given policy spec. It calls testFunc to run the test in the created
|
|
// environment.
|
|
func testWithRepoAndImagePolicy(
|
|
g *WithT,
|
|
fixture string,
|
|
policySpec imagev1_reflect.ImagePolicySpec,
|
|
latest string,
|
|
testFunc testWithRepoAndImagePolicyTestFunc) {
|
|
repositoryPath := "/config-" + randStringRunes(6) + ".git"
|
|
|
|
s := newRepoAndPolicyArgs()
|
|
|
|
// Create test git server.
|
|
gitServer, err := setupGitTestServer()
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create test git server")
|
|
defer os.RemoveAll(gitServer.Root())
|
|
defer gitServer.StopHTTP()
|
|
|
|
// Create test namespace.
|
|
nsCleanup, err := createNamespace(s.namespace)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create test namespace")
|
|
defer func() {
|
|
g.Expect(nsCleanup()).To(Succeed())
|
|
}()
|
|
|
|
// Create a git repo.
|
|
g.Expect(initGitRepo(gitServer, fixture, s.branch, repositoryPath)).To(Succeed())
|
|
|
|
// Clone the repo.
|
|
repoURL := gitServer.HTTPAddressWithCredentials() + repositoryPath
|
|
localRepo, err := cloneRepo(repoURL, s.branch)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to clone git repo")
|
|
|
|
// Create GitRepository resource for the above repo.
|
|
err = createGitRepository(s.gitRepoName, s.namespace, "", repoURL, "")
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create GitRepository resource")
|
|
|
|
// Create ImagePolicy with populated latest image in the status.
|
|
err = createImagePolicyWithLatestImageForSpec(s.imagePolicyName, s.namespace, policySpec, latest)
|
|
g.Expect(err).ToNot(HaveOccurred(), "failed to create ImagePolicy resource")
|
|
|
|
testFunc(g, s, repoURL, localRepo)
|
|
}
|
|
|
|
// setupGitTestServer creates and returns a git test server. The caller must
|
|
// ensure it's stopped and cleaned up.
|
|
func setupGitTestServer() (*gittestserver.GitServer, error) {
|
|
gitServer, err := gittestserver.NewTempGitServer()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
username := randStringRunes(5)
|
|
password := randStringRunes(5)
|
|
// Using authentication makes using the server more fiddly in
|
|
// general, but is required for testing SSH.
|
|
gitServer.Auth(username, password)
|
|
gitServer.AutoCreate()
|
|
if err := gitServer.StartHTTP(); err != nil {
|
|
return nil, err
|
|
}
|
|
gitServer.KeyDir(filepath.Join(gitServer.Root(), "keys"))
|
|
if err := gitServer.ListenSSH(); err != nil {
|
|
return nil, err
|
|
}
|
|
return gitServer, nil
|
|
}
|
|
|
|
// cleanup is used to return closures for cleaning up.
|
|
type cleanup func() error
|
|
|
|
// createNamespace creates a namespace and returns a closure for deleting the
|
|
// namespace.
|
|
func createNamespace(name string) (cleanup, error) {
|
|
namespace := &corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{Name: name},
|
|
}
|
|
if err := testEnv.Create(context.Background(), namespace); err != nil {
|
|
return nil, err
|
|
}
|
|
cleanup := func() error {
|
|
return testEnv.Delete(context.Background(), namespace)
|
|
}
|
|
return cleanup, nil
|
|
}
|
|
|
|
func cloneRepo(repoURL, branch string) (*git.Repository, error) {
|
|
return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
|
|
URL: repoURL,
|
|
RemoteName: "origin",
|
|
ReferenceName: plumbing.NewBranchReferenceName(branch),
|
|
})
|
|
}
|
|
|
|
func createGitRepository(name, namespace, impl, repoURL, secretRef string) error {
|
|
gitRepo := &sourcev1.GitRepository{
|
|
Spec: sourcev1.GitRepositorySpec{
|
|
URL: repoURL,
|
|
Interval: metav1.Duration{Duration: time.Minute},
|
|
},
|
|
}
|
|
gitRepo.Name = name
|
|
gitRepo.Namespace = namespace
|
|
if secretRef != "" {
|
|
gitRepo.Spec.SecretRef = &meta.LocalObjectReference{Name: secretRef}
|
|
}
|
|
if impl != "" {
|
|
gitRepo.Spec.GitImplementation = impl
|
|
}
|
|
return testEnv.Create(context.Background(), gitRepo)
|
|
}
|
|
|
|
func createImagePolicyWithLatestImage(name, namespace, repoRef, semverRange, latest string) error {
|
|
policySpec := imagev1_reflect.ImagePolicySpec{
|
|
ImageRepositoryRef: meta.NamespacedObjectReference{
|
|
Name: repoRef,
|
|
},
|
|
Policy: imagev1_reflect.ImagePolicyChoice{
|
|
SemVer: &imagev1_reflect.SemVerPolicy{
|
|
Range: semverRange,
|
|
},
|
|
},
|
|
}
|
|
return createImagePolicyWithLatestImageForSpec(name, namespace, policySpec, latest)
|
|
}
|
|
|
|
func createImagePolicyWithLatestImageForSpec(name, namespace string, policySpec imagev1_reflect.ImagePolicySpec, latest string) error {
|
|
policy := &imagev1_reflect.ImagePolicy{
|
|
Spec: policySpec,
|
|
}
|
|
policy.Name = name
|
|
policy.Namespace = namespace
|
|
err := testEnv.Create(context.Background(), policy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
policy.Status.LatestImage = latest
|
|
return testEnv.Status().Update(context.Background(), policy)
|
|
}
|
|
|
|
func updateImagePolicyWithLatestImage(name, namespace, latest string) error {
|
|
policy := &imagev1_reflect.ImagePolicy{}
|
|
key := types.NamespacedName{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
}
|
|
if err := testEnv.Get(context.Background(), key, policy); err != nil {
|
|
return err
|
|
}
|
|
policy.Status.LatestImage = latest
|
|
return testEnv.Status().Update(context.Background(), policy)
|
|
}
|
|
|
|
func createImageUpdateAutomation(
|
|
name, namespace, gitRepo, checkoutBranch, pushBranch, commitTemplate, signingKeyRef string,
|
|
updateStrategy *imagev1.UpdateStrategy) error {
|
|
updateAutomation := &imagev1.ImageUpdateAutomation{
|
|
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.
|
|
SourceRef: imagev1.SourceReference{
|
|
Kind: "GitRepository",
|
|
Name: gitRepo,
|
|
},
|
|
GitSpec: &imagev1.GitSpec{
|
|
Checkout: &imagev1.GitCheckoutSpec{
|
|
Reference: sourcev1.GitRepositoryRef{
|
|
Branch: checkoutBranch,
|
|
},
|
|
},
|
|
Commit: imagev1.CommitSpec{
|
|
MessageTemplate: commitTemplate,
|
|
Author: imagev1.CommitUser{
|
|
Name: testAuthorName,
|
|
Email: testAuthorEmail,
|
|
},
|
|
},
|
|
},
|
|
Update: updateStrategy,
|
|
},
|
|
}
|
|
updateAutomation.Name = name
|
|
updateAutomation.Namespace = namespace
|
|
if pushBranch != "" {
|
|
updateAutomation.Spec.GitSpec.Push = &imagev1.PushSpec{
|
|
Branch: pushBranch,
|
|
}
|
|
}
|
|
if signingKeyRef != "" {
|
|
updateAutomation.Spec.GitSpec.Commit.SigningKey = &imagev1.SigningKey{
|
|
SecretRef: meta.LocalObjectReference{Name: signingKeyRef},
|
|
}
|
|
}
|
|
return testEnv.Create(context.Background(), updateAutomation)
|
|
}
|
|
|
|
func deleteImageUpdateAutomation(name, namespace string) error {
|
|
update := &imagev1.ImageUpdateAutomation{}
|
|
update.Name = name
|
|
update.Namespace = namespace
|
|
return testEnv.Delete(context.Background(), update)
|
|
}
|
|
|
|
func deleteImagePolicy(name, namespace string) error {
|
|
imagePolicy := &imagev1_reflect.ImagePolicy{}
|
|
imagePolicy.Name = name
|
|
imagePolicy.Namespace = namespace
|
|
return testEnv.Delete(context.Background(), imagePolicy)
|
|
}
|
|
|
|
func createSigningKeyPair(name, namespace string) (*openpgp.Entity, error) {
|
|
pgpEntity, err := openpgp.NewEntity("", "", "", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Configure OpenPGP armor encoder.
|
|
b := bytes.NewBuffer(nil)
|
|
w, err := armor.Encode(b, openpgp.PrivateKeyType, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Serialize private key.
|
|
if err := pgpEntity.SerializePrivate(w, nil); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = w.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
// Create the secret containing signing key.
|
|
sec := &corev1.Secret{
|
|
Data: map[string][]byte{
|
|
"git.asc": b.Bytes(),
|
|
},
|
|
}
|
|
sec.Name = name
|
|
sec.Namespace = namespace
|
|
if err := testEnv.Create(ctx, sec); err != nil {
|
|
return nil, err
|
|
}
|
|
return pgpEntity, nil
|
|
}
|
|
|
|
func createSSHIdentitySecret(name, namespace, repoURL string) error {
|
|
url, err := url.Parse(repoURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
knownhosts, err := ssh.ScanHostKey(url.Host, 5*time.Second)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keygen := ssh.NewRSAGenerator(2048)
|
|
pair, err := keygen.Generate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sec := &corev1.Secret{
|
|
StringData: map[string]string{
|
|
"known_hosts": string(knownhosts),
|
|
"identity": string(pair.PrivateKey),
|
|
"identity.pub": string(pair.PublicKey),
|
|
},
|
|
}
|
|
sec.Name = name
|
|
sec.Namespace = namespace
|
|
return testEnv.Create(ctx, sec)
|
|
}
|
|
|
|
func getRepoURL(gitServer *gittestserver.GitServer, repoPath, proto string) (string, error) {
|
|
if proto == "http" {
|
|
return gitServer.HTTPAddressWithCredentials() + repoPath, nil
|
|
} else if proto == "ssh" {
|
|
return getSSHRepoURL(gitServer.SSHAddress(), repoPath), nil
|
|
}
|
|
return "", fmt.Errorf("proto not set to http or ssh")
|
|
}
|
|
|
|
func getSSHRepoURL(sshAddress, repoPath string) string {
|
|
// This is expected to use 127.0.0.1, but host key
|
|
// checking usually wants a hostname, so use
|
|
// "localhost".
|
|
sshURL := strings.Replace(sshAddress, "127.0.0.1", "localhost", 1)
|
|
return sshURL + repoPath
|
|
}
|