Skip to content

Commit e5f7ae4

Browse files
committed
Add dryrun CLI flag to read cluster resources
ref: https://issues.redhat.com/browse/ACM-22932 Instead of providing input yaml files to the dryrun command args, set the --from-cluster flag to read cluster resources via default kubeconfig. As before, the policies are patched with remediationAction: Inform, preventing modifications to the cluster. The fake runtime client will evaluate the policy. Signed-off-by: Janelle Law <[email protected]>
1 parent 2d3a0c7 commit e5f7ae4

File tree

2 files changed

+100
-40
lines changed

2 files changed

+100
-40
lines changed

pkg/dryrun/cmd.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type DryRunner struct {
2121
logPath string
2222
noColors bool
2323
fullDiffs bool
24+
fromCluster bool
2425
}
2526

2627
var ErrNonCompliant = errors.New("policy is NonCompliant")
@@ -105,6 +106,15 @@ func (d *DryRunner) GetCmd() *cobra.Command {
105106
"the DRYRUN_MAPPINGS_FILE environment variable.",
106107
)
107108

109+
cmd.Flags().BoolVar(
110+
&d.fromCluster,
111+
"from-cluster",
112+
false,
113+
"Read the current state of resources from a real Kubernetes cluster instead of "+
114+
"from input files. Uses the default kubeconfig or KUBECONFIG environment variable. "+
115+
"Any input files representing the cluster state will be ignored.",
116+
)
117+
108118
cmd.AddCommand(&cobra.Command{
109119
Use: "generate",
110120
Short: "Generate an API Mappings file",

pkg/dryrun/dryrun.go

Lines changed: 90 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ import (
3030
"k8s.io/apimachinery/pkg/types"
3131
"k8s.io/client-go/dynamic"
3232
dynfake "k8s.io/client-go/dynamic/fake"
33+
"k8s.io/client-go/kubernetes"
3334
clientsetfake "k8s.io/client-go/kubernetes/fake"
3435
"k8s.io/client-go/kubernetes/scheme"
36+
"k8s.io/client-go/tools/clientcmd"
3537
"k8s.io/client-go/tools/record"
3638
klog "k8s.io/klog/v2"
3739
parentpolicyv1 "open-cluster-management.io/governance-policy-propagator/api/v1"
@@ -57,11 +59,6 @@ func (d *DryRunner) dryRun(cmd *cobra.Command, args []string) error {
5759
return fmt.Errorf("unable to read input policy: %w", err)
5860
}
5961

60-
inputObjects, err := d.readInputResources(cmd, args)
61-
if err != nil {
62-
return fmt.Errorf("unable to read input resources: %w", err)
63-
}
64-
6562
if err := d.setupLogs(); err != nil {
6663
return fmt.Errorf("unable to setup the logging configuration: %w", err)
6764
}
@@ -74,9 +71,16 @@ func (d *DryRunner) dryRun(cmd *cobra.Command, args []string) error {
7471
return fmt.Errorf("unable to setup the dryrun reconciler: %w", err)
7572
}
7673

77-
err = d.applyInputResources(ctx, rec, inputObjects)
78-
if err != nil {
79-
return fmt.Errorf("unable to apply input resources: %w", err)
74+
if !d.fromCluster {
75+
inputObjects, err := d.readInputResources(cmd, args)
76+
if err != nil {
77+
return fmt.Errorf("unable to read input resources: %w", err)
78+
}
79+
80+
err = d.applyInputResources(ctx, rec, inputObjects)
81+
if err != nil {
82+
return fmt.Errorf("unable to apply input resources: %w", err)
83+
}
8084
}
8185

8286
cfgPolicyNN := types.NamespacedName{
@@ -332,10 +336,10 @@ func (d *DryRunner) readInputResources(cmd *cobra.Command, args []string) (
332336
return rawInputs, nil
333337
}
334338

339+
// applyInputResources applies the user's resources to the fake cluster
335340
func (d *DryRunner) applyInputResources(
336341
ctx context.Context, rec *ctrl.ConfigurationPolicyReconciler, inputObjects []*unstructured.Unstructured,
337342
) error {
338-
// Apply the user's resources to the fake cluster
339343
for _, obj := range inputObjects {
340344
gvk := obj.GroupVersionKind()
341345

@@ -428,11 +432,60 @@ func (d *DryRunner) setupReconciler(
428432
return nil, err
429433
}
430434

431-
dynamicClient := dynfake.NewSimpleDynamicClient(scheme.Scheme)
432-
clientset := clientsetfake.NewSimpleClientset()
435+
runtimeClient := clientfake.NewClientBuilder().
436+
WithScheme(scheme.Scheme).
437+
WithObjects(configPolCRD, cfgPolicy).
438+
WithStatusSubresource(cfgPolicy).
439+
Build()
440+
441+
nsSelUpdatesChan := make(chan event.GenericEvent, 20)
442+
433443
watcherReconciler, _ := depclient.NewControllerRuntimeSource()
434444

435-
dynamicWatcher := depclient.NewWithClients(
445+
var dynamicWatcher depclient.DynamicWatcher
446+
var clientset kubernetes.Interface
447+
var dynamicClient dynamic.Interface
448+
var nsSelReconciler common.NamespaceSelectorReconciler
449+
450+
if d.fromCluster {
451+
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
452+
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
453+
loadingRules, &clientcmd.ConfigOverrides{},
454+
)
455+
456+
kubeConfig, err := clientConfig.ClientConfig()
457+
if err != nil {
458+
return nil, fmt.Errorf("unable to locate the default kubeconfig: %w", err)
459+
}
460+
461+
clientset, err = kubernetes.NewForConfig(kubeConfig)
462+
if err != nil {
463+
return nil, fmt.Errorf("failed to create kubernetes clientset for cluster: %w", err)
464+
}
465+
466+
dynamicClient, err = dynamic.NewForConfig(kubeConfig)
467+
if err != nil {
468+
return nil, fmt.Errorf("failed to create dynamic client for cluster: %w", err)
469+
}
470+
471+
readOnlyMode := true // Prevent modifications to the cluster
472+
473+
realRuntimeClient, err := client.New(kubeConfig, client.Options{
474+
Scheme: scheme.Scheme,
475+
DryRun: &readOnlyMode,
476+
})
477+
if err != nil {
478+
return nil, fmt.Errorf("failed to create runtime client for ns selector reconciler: %w", err)
479+
}
480+
481+
nsSelReconciler = common.NewNamespaceSelectorReconciler(realRuntimeClient, nsSelUpdatesChan)
482+
} else {
483+
dynamicClient = dynfake.NewSimpleDynamicClient(scheme.Scheme)
484+
clientset = clientsetfake.NewSimpleClientset()
485+
nsSelReconciler = common.NewNamespaceSelectorReconciler(runtimeClient, nsSelUpdatesChan)
486+
}
487+
488+
dynamicWatcher = depclient.NewWithClients(
436489
dynamicClient,
437490
clientset.Discovery(),
438491
watcherReconciler,
@@ -446,14 +499,28 @@ func (d *DryRunner) setupReconciler(
446499
}
447500
}()
448501

449-
runtimeClient := clientfake.NewClientBuilder().
450-
WithScheme(scheme.Scheme).
451-
WithObjects(configPolCRD, cfgPolicy).
452-
WithStatusSubresource(cfgPolicy).
453-
Build()
502+
rec := ctrl.ConfigurationPolicyReconciler{
503+
Client: runtimeClient,
504+
DecryptionConcurrency: 1,
505+
DynamicWatcher: dynamicWatcher,
506+
Scheme: scheme.Scheme,
507+
Recorder: record.NewFakeRecorder(8),
508+
InstanceName: "policy-cli",
509+
TargetK8sClient: clientset,
510+
TargetK8sDynamicClient: dynamicClient,
511+
SelectorReconciler: &nsSelReconciler,
512+
EnableMetrics: false,
513+
UninstallMode: false,
514+
EvalBackoffSeconds: 5,
515+
FullDiffs: d.fullDiffs,
516+
}
454517

455-
nsSelUpdatesChan := make(chan event.GenericEvent, 20)
456-
nsSelReconciler := common.NewNamespaceSelectorReconciler(runtimeClient, nsSelUpdatesChan)
518+
// wait for dynamic watcher to have started
519+
<-rec.DynamicWatcher.Started()
520+
521+
if d.fromCluster {
522+
return &rec, nil
523+
}
457524

458525
defaultNs := &unstructured.Unstructured{
459526
Object: map[string]interface{}{
@@ -478,21 +545,7 @@ func (d *DryRunner) setupReconciler(
478545
return nil, err
479546
}
480547

481-
rec := ctrl.ConfigurationPolicyReconciler{
482-
Client: runtimeClient,
483-
DecryptionConcurrency: 1,
484-
DynamicWatcher: dynamicWatcher,
485-
Scheme: scheme.Scheme,
486-
Recorder: record.NewFakeRecorder(8),
487-
InstanceName: "policy-cli",
488-
TargetK8sClient: clientset,
489-
TargetK8sDynamicClient: dynamicClient,
490-
SelectorReconciler: &nsSelReconciler,
491-
EnableMetrics: false,
492-
UninstallMode: false,
493-
EvalBackoffSeconds: 5,
494-
FullDiffs: d.fullDiffs,
495-
}
548+
fakeClientset := clientset.(*clientsetfake.Clientset)
496549

497550
if d.mappingsPath != "" {
498551
mFile, err := os.ReadFile(d.mappingsPath)
@@ -505,19 +558,16 @@ func (d *DryRunner) setupReconciler(
505558
return nil, err
506559
}
507560

508-
clientset.Resources = mappings.ResourceLists(apiMappings)
561+
fakeClientset.Resources = mappings.ResourceLists(apiMappings)
509562
} else {
510-
clientset.Resources, err = mappings.DefaultResourceLists()
563+
fakeClientset.Resources, err = mappings.DefaultResourceLists()
511564
if err != nil {
512565
return nil, err
513566
}
514567
}
515568

516569
// Add open-cluster-management policy CRD
517-
addSupportedResources(clientset)
518-
519-
// wait for dynamic watcher to have started
520-
<-rec.DynamicWatcher.Started()
570+
addSupportedResources(fakeClientset)
521571

522572
return &rec, nil
523573
}

0 commit comments

Comments
 (0)