Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 225 additions & 0 deletions e2e-tests/framework/resources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package framework

import (
"errors"
"fmt"
"log"
"strings"
)

type Resource interface {
Delete(k KubectlRunner) error
Create(k KubectlRunner) error
}

func ResourceCleanup(clusters []KubectlRunner, resources []Resource) error {
var errs []error
for _, k := range clusters {
for _, r := range resources {
if err := r.Delete(k); err != nil {
errs = append(errs, err)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
return errors.Join(errs...)
}

type ClusterRole struct {
Name string
Verb string
Resource string
Label string
}

func (cr ClusterRole) Create(k KubectlRunner) error {
_, err := k.Run("create", "clusterrole", cr.Name, "--verb="+cr.Verb, "--resource="+cr.Resource)
if err != nil {
return fmt.Errorf("failed to create ClusterRole %s: %w", cr.Name, err)
}
log.Printf("created ClusterRole %s", cr.Name)
if cr.Label != "" {
_, err = k.Run("label", "clusterrole", cr.Name, cr.Label)
if err != nil {
return fmt.Errorf("failed to label ClusterRole %s: %w", cr.Name, err)
}
}
return nil
}

func (cr ClusterRole) Delete(k KubectlRunner) error {
_, err := k.Run("delete", "clusterrole", cr.Name, "--ignore-not-found=true")
if err != nil {
return fmt.Errorf("failed to delete ClusterRole %s: %w", cr.Name, err)
}
return nil
}

func (cr CustomResource) AssertField(k KubectlRunner, jsonpath, expected string) error {
val, err := k.Run("get", strings.ToLower(cr.Kind), cr.Name, "-n", cr.Namespace, "-o", "jsonpath="+jsonpath)
if err != nil {
return fmt.Errorf("failed to get %s %s field %s: %w", cr.Kind, cr.Name, jsonpath, err)
}
if val != expected {
return fmt.Errorf("%s %s field %s: expected %q, got %q", cr.Kind, cr.Name, jsonpath, expected, val)
}
return nil
}

type ClusterRoleBinding struct {
Name string
ClusterRoleName string
Label string
}

func (crb ClusterRoleBinding) Create(k KubectlRunner) error {
_, err := k.Run("create", "clusterrolebinding", crb.Name, "--clusterrole="+crb.ClusterRoleName)
if err != nil {
return fmt.Errorf("failed to create ClusterRoleBinding %s: %w", crb.Name, err)
}
log.Printf("created ClusterRoleBinding %s -> ClusterRole %s", crb.Name, crb.ClusterRoleName)
if crb.Label != "" {
_, err = k.Run("label", "clusterrolebinding", crb.Name, crb.Label)
if err != nil {
return fmt.Errorf("failed to label ClusterRoleBinding %s: %w", crb.Name, err)
}
}
return nil
}

func (crb ClusterRoleBinding) Delete(k KubectlRunner) error {
_, err := k.Run("delete", "clusterrolebinding", crb.Name, "--ignore-not-found=true")
if err != nil {
return fmt.Errorf("failed to delete ClusterRoleBinding %s: %w", crb.Name, err)
}
return nil
}

func (crb ClusterRoleBinding) AddSubject(k KubectlRunner, sa ServiceAccount) error {
subject := fmt.Sprintf(`{"kind":"ServiceAccount","name":"%s","namespace":"%s"}`, sa.Name, sa.Namespace)

out, err := k.Run("get", "clusterrolebinding", crb.Name, "-o", "jsonpath={.subjects}")
if err != nil {
return fmt.Errorf("failed to check subjects on CRB %s: %w", crb.Name, err)
}

var patch string
if out == "" {
patch = fmt.Sprintf(`[{"op":"add","path":"/subjects","value":[%s]}]`, subject)
} else {
patch = fmt.Sprintf(`[{"op":"add","path":"/subjects/-","value":%s}]`, subject)
}

_, err = k.Run("patch", "clusterrolebinding", crb.Name, "--type=json", "-p", patch)
if err != nil {
return fmt.Errorf("failed to add subject to CRB %s: %w", crb.Name, err)
}
return nil
}

type ServiceAccount struct {
Name string
Namespace string
}

func (sa ServiceAccount) Create(k KubectlRunner) error {
_, err := k.Run("create", "serviceaccount", sa.Name, "-n", sa.Namespace)
if err != nil {
return fmt.Errorf("failed to create ServiceAccount %s in %s: %w", sa.Name, sa.Namespace, err)
}
log.Printf("created ServiceAccount %s in %s", sa.Name, sa.Namespace)
return nil
}

func (sa ServiceAccount) Delete(k KubectlRunner) error {
_, err := k.Run("delete", "serviceaccount", sa.Name, "-n", sa.Namespace, "--ignore-not-found=true")
if err != nil {
return fmt.Errorf("failed to delete ServiceAccount %s: %w", sa.Name, err)
}
return nil
}

type CustomResourceDefinition struct {
Name string
YAML string
}

func (crd CustomResourceDefinition) Create(k KubectlRunner) error {
_, err := k.RunWithStdin(crd.YAML, "apply", "-f", "-")
if err != nil {
return fmt.Errorf("failed to create CRD %s: %w", crd.Name, err)
}
log.Printf("created CRD %s", crd.Name)
return nil
}

func (crd CustomResourceDefinition) Delete(k KubectlRunner) error {
_, err := k.Run("delete", "crd", crd.Name, "--ignore-not-found=true")
if err != nil {
return fmt.Errorf("failed to delete CRD %s: %w", crd.Name, err)
}
return nil
}

func (crd CustomResourceDefinition) WaitForEstablished(k KubectlRunner) error {
_, err := k.Run("wait", "--for=condition=Established", "crd/"+crd.Name, "--timeout=30s")
if err != nil {
return fmt.Errorf("CRD %s not established: %w", crd.Name, err)
}
log.Printf("CRD %s is Established", crd.Name)
return nil
}

type CustomResource struct {
Name string
Namespace string
Kind string
Resource string
YAML string
}

func (cr CustomResource) Create(k KubectlRunner) error {
_, err := k.RunWithStdin(cr.YAML, "apply", "-f", "-", "-n", cr.Namespace)
if err != nil {
return fmt.Errorf("failed to create %s %s: %w", cr.Kind, cr.Name, err)
}
log.Printf("created %s %s in %s", cr.Kind, cr.Name, cr.Namespace)
return nil
}

func (cr CustomResource) Delete(k KubectlRunner) error {
if cr.Resource == "" {
return fmt.Errorf("failed to delete %s %s: missing API resource name for kind %s", cr.Kind, cr.Name, cr.Kind)
}
_, err := k.Run("delete", cr.Resource, cr.Name, "-n", cr.Namespace, "--ignore-not-found=true")
if err != nil {
return fmt.Errorf("failed to delete %s %s (api resource %s): %w", cr.Kind, cr.Name, cr.Resource, err)
}
return nil
}

type Namespace struct {
Name string
Label string
}

func (n Namespace) Create(k KubectlRunner) error {
if err := k.CreateNamespace(n.Name); err != nil {
return fmt.Errorf("failed to create namespace %s: %w", n.Name, err)
}
log.Printf("created namespace %s", n.Name)
if n.Label != "" {
_, err := k.Run("label", "namespace", n.Name, n.Label)
if err != nil {
return fmt.Errorf("failed to label namespace %s: %w", n.Name, err)
}
}
return nil
}

func (n Namespace) Delete(k KubectlRunner) error {
_, err := k.Run("delete", "namespace", n.Name, "--ignore-not-found=true", "--wait=true", "--timeout=60s")
if err != nil {
return fmt.Errorf("failed to delete namespace %s: %w", n.Name, err)
}
return nil
}
49 changes: 48 additions & 1 deletion e2e-tests/framework/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package framework

import (
"fmt"
"log"
"regexp"
"slices"
"strings"

"github.com/onsi/ginkgo/v2"
Expand Down Expand Up @@ -37,4 +39,49 @@ func RegisterMTAResultReporter() {
fmt.Printf("\n[CRANE-RESULT] SKIPPED %s\n", id)
}
})
}
}

type ExpectedClusterRoleBinding struct {
ClusterRoleBindingName string
ClusterRoleName string
SubjectName string
}

func ValidateClusterRBAC(kubectl KubectlRunner, bindings []ExpectedClusterRoleBinding) error {
clusterRoles := map[string]bool{}
for _, b := range bindings {
clusterRoles[b.ClusterRoleName] = true
}
for cr := range clusterRoles {
if _, err := kubectl.Run("get", "clusterrole", cr); err != nil {
return fmt.Errorf("ClusterRole %s not found: %w", cr, err)
}
log.Printf("ClusterRole %s exists", cr)
}

for _, b := range bindings {
if _, err := kubectl.Run("get", "clusterrolebinding", b.ClusterRoleBindingName); err != nil {
return fmt.Errorf("ClusterRoleBinding %s not found: %w", b.ClusterRoleBindingName, err)
}

roleRef, err := kubectl.Run("get", "clusterrolebinding", b.ClusterRoleBindingName, "-o", "jsonpath={.roleRef.name}")
if err != nil {
return fmt.Errorf("failed to get roleRef for CRB %s: %w", b.ClusterRoleBindingName, err)
}
if roleRef != b.ClusterRoleName {
return fmt.Errorf("CRB %s references %s, expected %s", b.ClusterRoleBindingName, roleRef, b.ClusterRoleName)
}

subjectOutput, err := kubectl.Run("get", "clusterrolebinding", b.ClusterRoleBindingName, "-o", "jsonpath={.subjects[*].name}")
if err != nil {
return fmt.Errorf("failed to get subject for CRB %s: %w", b.ClusterRoleBindingName, err)
}
subjects := strings.Fields(subjectOutput)

if !slices.Contains(subjects, b.SubjectName) {
return fmt.Errorf("CRB %s subject is %s, expected %s", b.ClusterRoleBindingName, subjectOutput, b.SubjectName)
}
log.Printf("CRB %s -> CR %s (subject: %s) verified", b.ClusterRoleBindingName, b.ClusterRoleName, b.SubjectName)
}
return nil
}
7 changes: 7 additions & 0 deletions e2e-tests/testdata/widget_cr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: crane-e2e.example.com/v1
kind: Widget
metadata:
name: test-widget
spec:
color: blue
size: 5
27 changes: 27 additions & 0 deletions e2e-tests/testdata/widget_crd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: widgets.crane-e2e.example.com
spec:
group: crane-e2e.example.com
names:
kind: Widget
listKind: WidgetList
plural: widgets
singular: widget
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
color:
type: string
size:
type: integer
60 changes: 60 additions & 0 deletions e2e-tests/tests/tier0/mta_851_no_cluster_resources_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package e2e

import (
"log"
"os"
"path/filepath"

"github.com/konveyor/crane/e2e-tests/config"
. "github.com/konveyor/crane/e2e-tests/framework"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Cluster-level export control", func() {
It("[MTA-851] Should produce no _cluster output for namespace-only workload", Label("tier0"), func() {
appName := "simple-nginx-nopv"
namespace := "simple-nginx-nopv"
serviceName := "my-" + appName
scenario := NewMigrationScenario(
appName,
namespace,
config.K8sDeployBin,
config.CraneBin,
config.SourceContext,
config.TargetContext,
)
srcApp := scenario.SrcApp
tgtApp := scenario.TgtApp
kubectlSrc := scenario.KubectlSrc
paths, err := NewScenarioPaths("crane-ca5-*")
Expect(err).NotTo(HaveOccurred())
runner := scenario.Crane
exportOpts := ExportOptions{Namespace: srcApp.Namespace, ExportDir: paths.ExportDir}
transformOpts := TransformOptions{ExportDir: paths.ExportDir, TransformDir: paths.TransformDir}
applyOpts := ApplyOptions{ExportDir: paths.ExportDir, TransformDir: paths.TransformDir,
OutputDir: paths.OutputDir}

DeferCleanup(func() {
if err := CleanupScenario(paths.TempDir, srcApp, tgtApp); err != nil {
log.Printf("cleanup: %v", err)
}
})
Comment thread
RanWurmbrand marked this conversation as resolved.

By("Deploying namespace-only app on source cluster")
Expect(PrepareSourceApp(srcApp, kubectlSrc)).NotTo(HaveOccurred())

By("Waiting for source pods and endpoints to drain")
WaitForSourceQuiesce(kubectlSrc, namespace, "app="+appName, serviceName)

By("Running crane export, transform, apply")
Expect(RunCranePipelineWithChecks(runner, exportOpts, transformOpts, applyOpts)).NotTo(HaveOccurred())

By("Verifying no _cluster directory to be created")
_, err = os.Stat(filepath.Join(paths.ExportDir, "resources", namespace, "_cluster"))
// _cluster directory is being created only when cluster Resources are present
Expect(err).To(HaveOccurred())

})

})
Loading
Loading