Skip to content

Commit

Permalink
Merge pull request GoogleCloudPlatform#1394 from acpana/tests_for_fix…
Browse files Browse the repository at this point in the history
…_enum_casing-r

chore: Add tests for CCC stateIntoSpec
  • Loading branch information
google-oss-prow[bot] authored Mar 26, 2024
2 parents 45326d9 + 15d70be commit f6a47be
Show file tree
Hide file tree
Showing 17 changed files with 1,165 additions and 96 deletions.
60 changes: 39 additions & 21 deletions config/tests/samples/create/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ type Harness struct {
*testing.T
Ctx context.Context

Events *test.MemoryEventSink
Project testgcp.GCPProject
Events *test.MemoryEventSink
Project testgcp.GCPProject
VCRRecorder *recorder.Recorder

client client.Client
restConfig *rest.Config
Expand All @@ -76,7 +77,8 @@ type Harness struct {
gcpAccessToken string
kccConfig kccmanager.Config

VCRRecorder *recorder.Recorder
// goldenFiles tracks the golden files we checked, so we can look for "extra" golden files.
goldenFiles []string
}

type httpRoundTripperKeyType int
Expand Down Expand Up @@ -432,6 +434,8 @@ func MaybeSkip(t *testing.T, name string, resources []*unstructured.Unstructured
gvk := resource.GroupVersionKind()

switch gvk.Group {
case "core.cnrm.cloud.google.com":
continue
case "certificatemanager.cnrm.cloud.google.com":
continue
}
Expand Down Expand Up @@ -528,31 +532,45 @@ func (h *Harness) waitForCRDReady(obj client.Object) {
}
}

func (h *Harness) CompareGoldenFile(p string, got string, normalizers ...func(s string) string) {
test.CompareGoldenFile(h.T, p, got, normalizers...)
}
func (h *Harness) NoExtraGoldenFiles(glob string) {
gotFiles, err := filepath.Glob(glob)
if err != nil {
h.Fatalf("error matching glob %q: %v", glob, err)
}

func (h *Harness) MustReadFile(p string) []byte {
return test.MustReadFile(h.T, p)
}
goldenFilesSet := sets.New(h.goldenFiles...)

// IgnoreComments is a normalization function that strips comments.
func (h *Harness) IgnoreComments(s string) string {
lines := strings.Split(s, "\n")
for i, line := range lines {
if strings.HasPrefix(line, "#") {
lines[i] = ""
for _, gotFile := range gotFiles {
abs, err := filepath.Abs(gotFile)
if err != nil {
h.Fatalf("error getting absolute path for %q: %v", gotFile, err)
}
if goldenFilesSet.Has(abs) {
continue
}

h.Errorf("found extra file %q", gotFile)

if os.Getenv("WRITE_GOLDEN_OUTPUT") != "" {
if err := os.Remove(abs); err != nil {
h.Errorf("error removing extra file %q", abs)
}
}
}
s = strings.Join(lines, "\n")
return strings.TrimSpace(s)
}

// ReplaceString is a normalization function that replaces a string, useful for e.g. project IDs.
func (h *Harness) ReplaceString(from, to string) func(string) string {
return func(s string) string {
return strings.ReplaceAll(s, from, to)
func (h *Harness) CompareGoldenFile(p string, got string, normalizers ...func(s string) string) {
abs, err := filepath.Abs(p)
if err != nil {
h.Fatalf("error converting path %q to absolute path: %v", p, err)
}
h.goldenFiles = append(h.goldenFiles, abs)

test.CompareGoldenFile(h.T, p, got, normalizers...)
}

func (h *Harness) MustReadFile(p string) []byte {
return test.MustReadFile(h.T, p)
}

func filterLogs(log logr.Logger) logr.Logger {
Expand Down
25 changes: 19 additions & 6 deletions config/tests/samples/create/samples.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/ghodss/yaml"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -113,7 +114,7 @@ func RunCreateDeleteTest(t *Harness, opt CreateDeleteTestOptions) {
}

if !opt.SkipWaitForReady {
waitForReady(t, opt.Create)
WaitForReady(t, opt.Create...)
}

if len(opt.Updates) != 0 {
Expand All @@ -125,7 +126,7 @@ func RunCreateDeleteTest(t *Harness, opt CreateDeleteTestOptions) {
}

if !opt.SkipWaitForReady {
waitForReady(t, opt.Updates)
WaitForReady(t, opt.Updates...)
}
}

Expand All @@ -135,20 +136,29 @@ func RunCreateDeleteTest(t *Harness, opt CreateDeleteTestOptions) {
}
}

func waitForReady(t *Harness, unstructs []*unstructured.Unstructured) {
func WaitForReady(h *Harness, unstructs ...*unstructured.Unstructured) {
var wg sync.WaitGroup
for _, u := range unstructs {
u := u
wg.Add(1)
go waitForReadySingleResource(t, &wg, u)
go func() {
defer wg.Done()
waitForReadySingleResource(h, u)
}()
}
wg.Wait()
}

func waitForReadySingleResource(t *Harness, wg *sync.WaitGroup, u *unstructured.Unstructured) {
func waitForReadySingleResource(t *Harness, u *unstructured.Unstructured) {
logger := log.FromContext(t.Ctx)

switch u.GroupVersionKind().GroupKind() {
case schema.GroupKind{Group: "core.cnrm.cloud.google.com", Kind: "ConfigConnectorContext"}:
logger.Info("ConfigConnectorContext object does not having status.conditions; assuming ready")
return
}

name := k8s.GetNamespacedName(u)
defer wg.Done()
err := wait.PollImmediate(1*time.Second, 35*time.Minute, func() (done bool, err error) {
done = true
logger.V(2).Info("Testing to see if resource is ready", "kind", u.GetKind(), "name", u.GetName())
Expand Down Expand Up @@ -217,6 +227,9 @@ func DeleteResources(t *Harness, opts CreateDeleteTestOptions) {
for _, u := range unstructs {
logger.Info("Deleting resource", "kind", u.GetKind(), "name", u.GetName())
if err := t.GetClient().Delete(t.Ctx, u); err != nil {
if apierrors.IsNotFound(err) {
continue
}
t.Errorf("error deleting: %v", err)
}
}
Expand Down
34 changes: 12 additions & 22 deletions pkg/test/eventsink.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,47 +73,37 @@ func (s *MemoryEventSink) AddHTTPEvent(ctx context.Context, entry *LogEntry) { /
s.HTTPEvents = append(s.HTTPEvents, entry)
}

func (s *MemoryEventSink) FormatHTTP() string {
s.mutex.Lock()
defer s.mutex.Unlock()

func (s LogEntries) FormatHTTP() string {
var eventStrings []string
for _, entry := range s.HTTPEvents {
for _, entry := range s {
s := entry.FormatHTTP()
eventStrings = append(eventStrings, s)
}
return strings.Join(eventStrings, "\n---\n\n")
}

func (s *MemoryEventSink) PrettifyJSON(mutators ...JSONMutator) {
s.mutex.Lock()
defer s.mutex.Unlock()
type LogEntries []*LogEntry

for _, entry := range s.HTTPEvents {
func (s *LogEntries) PrettifyJSON(mutators ...JSONMutator) {
for _, entry := range *s {
entry.PrettifyJSON(mutators...)
}
}

func (s *MemoryEventSink) RemoveHTTPResponseHeader(key string) {
s.mutex.Lock()
defer s.mutex.Unlock()

for _, entry := range s.HTTPEvents {
func (s *LogEntries) RemoveHTTPResponseHeader(key string) {
for _, entry := range *s {
entry.Response.RemoveHeader(key)
}
}

func (s *MemoryEventSink) RemoveRequests(pred func(e *LogEntry) bool) {
s.mutex.Lock()
defer s.mutex.Unlock()

var keep []*LogEntry
for _, entry := range s.HTTPEvents {
if !pred(entry) {
func (s LogEntries) KeepIf(pred func(e *LogEntry) bool) LogEntries {
var keep LogEntries
for _, entry := range s {
if pred(entry) {
keep = append(keep, entry)
}
}
s.HTTPEvents = keep
return keep
}

type DirectoryEventSink struct {
Expand Down
97 changes: 97 additions & 0 deletions tests/e2e/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2024 Google LLC
//
// 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 e2e

import (
"path/filepath"
"strings"

"github.com/GoogleCloudPlatform/k8s-config-connector/config/tests/samples/create"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/cli/cmd/export"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2"
"sigs.k8s.io/yaml"
)

func exportResource(h *create.Harness, obj *unstructured.Unstructured) string {
exportURI := ""

projectID := obj.GetAnnotations()["cnrm.cloud.google.com/project-id"]
if projectID == "" {
projectID = h.Project.ProjectID
}
resourceID, _, _ := unstructured.NestedString(obj.Object, "spec", "resourceID")
if resourceID == "" {
resourceID = obj.GetName()
}
// location, _, _ := unstructured.NestedString(obj.Object, "spec", "location")

gvk := obj.GroupVersionKind()
switch gvk.GroupKind() {
case schema.GroupKind{Group: "serviceusage.cnrm.cloud.google.com", Kind: "Service"}:
exportURI = "//serviceusage.googleapis.com/projects/" + projectID + "/services/" + resourceID
// case schema.GroupKind{Group: "certificatemanager.cnrm.cloud.google.com", Kind: "CertificateManagerCertificate"}:
// exportURI = "//certificatemanager.googleapis.com/projects/" + projectID + "/locations/" + location + "/certificates/" + resourceID
// case schema.GroupKind{Group: "certificatemanager.cnrm.cloud.google.com", Kind: "CertificateManagerCertificateMap"}:
// if location == "" {
// location = "global"
// }
// exportURI = "//certificatemanager.googleapis.com/projects/" + projectID + "/locations/" + location + "/certificateMaps/" + resourceID
// case schema.GroupKind{Group: "certificatemanager.cnrm.cloud.google.com", Kind: "CertificateManagerCertificateMapEntry"}:
// exportURI = "//certificatemanager.googleapis.com/projects/" + projectID + "/locations/" + location + "/certificateMaps/" + certificateMapID + "/certificateMapEntries/" + resourceID
// TODO: This does not work
// case schema.GroupKind{Group: "iam.cnrm.cloud.google.com", Kind: "IAMServiceAccount"}:
// name := obj.GetName()
// exportURI = "//iam.googleapis.com/projects/" + projectID + "/serviceAccounts/" + name
case schema.GroupKind{Group: "bigquery.cnrm.cloud.google.com", Kind: "BigQueryDataset"}:
exportURI = "//bigquery.googleapis.com/projects/" + projectID + "/datasets/" + resourceID
}

if exportURI == "" {
return ""
}

exportParams := h.ExportParams()
exportParams.IAMFormat = "partialpolicy"
exportParams.ResourceFormat = "krm"
outputDir := h.TempDir()
outputPath := filepath.Join(outputDir, "export.yaml")
exportParams.Output = outputPath
exportParams.URI = exportURI
if err := export.Execute(h.Ctx, &exportParams); err != nil {
h.Errorf("error from export.Execute: %v", err)
return ""
}

output := h.MustReadFile(outputPath)
return string(output)
}

func exportResourceAsUnstructured(h *create.Harness, obj *unstructured.Unstructured) *unstructured.Unstructured {
s := exportResource(h, obj)
if s == "" {
return nil
}
// TODO: Why are we outputing this prefix?
klog.Infof("exportResourceAsUnstructured %q", s)
s = strings.TrimPrefix(s, "----")
u := &unstructured.Unstructured{}
if err := yaml.Unmarshal([]byte(s), &u); err != nil {
h.Errorf("error from yaml.Unmarshal: %v", err)
return nil
}
return u
}
Loading

0 comments on commit f6a47be

Please sign in to comment.