Test that the tags for a repo are scanned

In this commit I use the test registry implementation to check that
the controller will scan the tags of an image. This needs a bit more
scaffolding, since the test registry doesn't handle /tags/list.
This commit is contained in:
Michael Bridgen 2020-07-12 14:08:16 +01:00
parent 31152814cc
commit 6ae6561b8b
3 changed files with 114 additions and 5 deletions

View File

@ -68,10 +68,6 @@ func (r *ImageRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er
canonicalName := ref.Context().String()
if imageRepo.Status.CanonicalImageName != canonicalName {
imageRepo.Status.CanonicalImageName = canonicalName
imageRepo.Status.LastError = ""
if err := r.Status().Update(ctx, &imageRepo); err != nil {
return ctrl.Result{}, err
}
}
now := time.Now()

View File

@ -18,8 +18,12 @@ package controllers
import (
"context"
"strings"
"time"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/random"
"github.com/google/go-containerregistry/pkg/v1/remote"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/types"
@ -28,8 +32,14 @@ import (
// +kubebuilder:scaffold:imports
)
// https://github.com/google/go-containerregistry/blob/v0.1.1/pkg/registry/compatibility_test.go
// has an example of loading a test registry with a random image.
var _ = Describe("ImageRepository controller", func() {
It("expands the canonical image name", func() {
// would be good to test this without needing to do the scanning, since
// 1. better to not rely on external services being available
// 2. probably going to want to have several test cases
repo := imagev1alpha1.ImageRepository{
Spec: imagev1alpha1.ImageRepositorySpec{
Image: "alpine",
@ -52,11 +62,51 @@ var _ = Describe("ImageRepository controller", func() {
var repoAfter imagev1alpha1.ImageRepository
Eventually(func() bool {
err = r.Get(context.Background(), imageRepoName, &repoAfter)
err := r.Get(context.Background(), imageRepoName, &repoAfter)
return err == nil && repoAfter.Status.CanonicalImageName != ""
}, timeout, interval).Should(BeTrue())
Expect(repoAfter.Name).To(Equal("alpine-image"))
Expect(repoAfter.Namespace).To(Equal("default"))
Expect(repoAfter.Status.CanonicalImageName).To(Equal("index.docker.io/library/alpine"))
})
It("fetches the tags for an image", func() {
versions := []string{"0.1.0", "0.1.1", "0.2.0", "1.0.0", "1.0.1", "1.0.2", "1.1.0-alpha"}
registry := strings.TrimPrefix(registryServer.URL, "http://")
imgRepo := registry + "/myimage"
for _, tag := range versions {
imgRef, err := name.NewTag(imgRepo + ":" + tag)
Expect(err).ToNot(HaveOccurred())
img, err := random.Image(512, 1)
Expect(err).ToNot(HaveOccurred())
Expect(remote.Write(imgRef, img)).To(Succeed())
}
repo := imagev1alpha1.ImageRepository{
Spec: imagev1alpha1.ImageRepositorySpec{
Image: imgRepo,
},
}
objectName := types.NamespacedName{
Name: "random",
Namespace: "default",
}
repo.Name = objectName.Name
repo.Namespace = objectName.Namespace
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
r := imageRepoReconciler
Expect(r.Create(ctx, &repo)).To(Succeed())
var repoAfter imagev1alpha1.ImageRepository
Eventually(func() bool {
err := r.Get(context.Background(), objectName, &repoAfter)
return err == nil && repoAfter.Status.CanonicalImageName != ""
}, timeout, interval).Should(BeTrue())
Expect(repoAfter.Status.CanonicalImageName).To(Equal(imgRepo))
Expect(repoAfter.Status.LastScanResult.TagCount).To(Equal(len(versions)))
})
})

View File

@ -17,10 +17,15 @@ limitations under the License.
package controllers
import (
"encoding/json"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"time"
"github.com/google/go-containerregistry/pkg/registry"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
@ -51,6 +56,7 @@ var k8sClient client.Client
var k8sMgr ctrl.Manager
var imageRepoReconciler *ImageRepositoryReconciler
var testEnv *envtest.Environment
var registryServer *httptest.Server
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
@ -101,6 +107,13 @@ var _ = BeforeSuite(func(done Done) {
k8sClient = k8sMgr.GetClient()
Expect(k8sClient).ToNot(BeNil())
// set up a local registry for testing scanning
regHandler := registry.New()
registryServer = httptest.NewServer(&tagListHandler{
registryHandler: regHandler,
imagetags: map[string][]string{},
})
close(done)
}, 60)
@ -108,4 +121,54 @@ var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
registryServer.Close()
})
// ---
// the go-containerregistry test regsitry implementation does not
// serve /myimage/tags/list. Until it does, I'm adding this handler.
// NB:
// - assumes repo name is a single element
// - assumes no overwriting tags
type tagListHandler struct {
registryHandler http.Handler
imagetags map[string][]string
}
type tagListResult struct {
Name string `json:"name"`
Tags []string `json:"tags"`
}
func (h *tagListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// a tag list request has a path like: /v2/<repo>/tags/list
if withoutTagsList := strings.TrimSuffix(r.URL.Path, "/tags/list"); r.Method == "GET" && withoutTagsList != r.URL.Path {
repo := strings.TrimPrefix(withoutTagsList, "/v2/")
if tags, ok := h.imagetags[repo]; ok {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
result := tagListResult{
Name: repo,
Tags: tags,
}
Expect(json.NewEncoder(w).Encode(result)).To(Succeed())
println("Requested tags", repo, strings.Join(tags, ", "))
return
}
w.WriteHeader(http.StatusNotFound)
return
}
// record the fact of a PUT to a tag; the path looks like: /v2/<repo>/manifests/<tag>
h.registryHandler.ServeHTTP(w, r)
if r.Method == "PUT" {
pathElements := strings.Split(r.URL.Path, "/")
if len(pathElements) == 5 && pathElements[1] == "v2" && pathElements[3] == "manifests" {
repo, tag := pathElements[2], pathElements[4]
println("Recording tag", repo, tag)
h.imagetags[repo] = append(h.imagetags[repo], tag)
}
}
}