Skip to content

Commit b50b62d

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 5774b0e commit b50b62d

File tree

2 files changed

+108
-43
lines changed

2 files changed

+108
-43
lines changed

pkg/dryrun/cmd.go

Lines changed: 13 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,18 @@ func (d *DryRunner) GetCmd() *cobra.Command {
105106
"the DRYRUN_MAPPINGS_FILE environment variable.",
106107
)
107108

109+
fromCluster := os.Getenv("DRYRUN_FROM_CLUSTER") == "true" // false if not set
110+
111+
cmd.Flags().BoolVar(
112+
&d.fromCluster,
113+
"from-cluster",
114+
fromCluster,
115+
"Read the current state of resources from a real Kubernetes cluster instead of "+
116+
"from input files. Uses the default kubeconfig or KUBECONFIG environment variable. "+
117+
"Any input files representing the cluster state will be ignored. "+
118+
"Can also be set via the DRYRUN_FROM_CLUSTER environment variable.",
119+
)
120+
108121
cmd.AddCommand(&cobra.Command{
109122
Use: "generate",
110123
Short: "Generate an API Mappings file",

pkg/dryrun/dryrun.go

Lines changed: 95 additions & 43 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,12 @@ 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(
336-
ctx context.Context, rec *ctrl.ConfigurationPolicyReconciler, inputObjects []*unstructured.Unstructured,
341+
ctx context.Context,
342+
rec *ctrl.ConfigurationPolicyReconciler,
343+
inputObjects []*unstructured.Unstructured,
337344
) error {
338-
// Apply the user's resources to the fake cluster
339345
for _, obj := range inputObjects {
340346
gvk := obj.GroupVersionKind()
341347

@@ -346,7 +352,7 @@ func (d *DryRunner) applyInputResources(
346352
"entry in the mappings file", err, gvk.Kind)
347353
}
348354

349-
return fmt.Errorf("unable to apply an input resource: %w", err)
355+
return err
350356
}
351357

352358
var resInt dynamic.ResourceInterface
@@ -365,7 +371,7 @@ func (d *DryRunner) applyInputResources(
365371

366372
if _, err := resInt.Create(ctx, obj, metav1.CreateOptions{}); err != nil &&
367373
!k8serrors.IsAlreadyExists(err) {
368-
return fmt.Errorf("unable to apply an input resource: %w", err)
374+
return err
369375
}
370376

371377
// Manually convert resources from the dynamic client to the runtime client
@@ -428,11 +434,60 @@ func (d *DryRunner) setupReconciler(
428434
return nil, err
429435
}
430436

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

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

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

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

458527
defaultNs := &unstructured.Unstructured{
459528
Object: map[string]interface{}{
@@ -478,21 +547,7 @@ func (d *DryRunner) setupReconciler(
478547
return nil, err
479548
}
480549

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-
}
550+
fakeClientset := clientset.(*clientsetfake.Clientset)
496551

497552
if d.mappingsPath != "" {
498553
mFile, err := os.ReadFile(d.mappingsPath)
@@ -505,19 +560,16 @@ func (d *DryRunner) setupReconciler(
505560
return nil, err
506561
}
507562

508-
clientset.Resources = mappings.ResourceLists(apiMappings)
563+
fakeClientset.Resources = mappings.ResourceLists(apiMappings)
509564
} else {
510-
clientset.Resources, err = mappings.DefaultResourceLists()
565+
fakeClientset.Resources, err = mappings.DefaultResourceLists()
511566
if err != nil {
512567
return nil, err
513568
}
514569
}
515570

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

522574
return &rec, nil
523575
}

0 commit comments

Comments
 (0)