Skip to content

Commit 3d09302

Browse files
jan-lawdhaiducek
authored andcommitted
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. Signed-off-by: Janelle Law <[email protected]> (cherry picked from commit 3886abc3524cabb2106f4da64958d7c75099121b)
1 parent 639ccfb commit 3d09302

File tree

2 files changed

+117
-43
lines changed

2 files changed

+117
-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 the currently configured Kubernetes cluster instead of "+
116+
"from input files. Uses the default kubeconfig or KUBECONFIG environment variable. "+
117+
"Any input files representing the cluster state are 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: 104 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,10 +434,34 @@ func (d *DryRunner) setupReconciler(
428434
return nil, err
429435
}
430436

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

444+
var clientset kubernetes.Interface
445+
var dynamicClient dynamic.Interface
446+
var nsSelReconciler common.NamespaceSelectorReconciler
447+
448+
if d.fromCluster {
449+
var nsSelClient client.Client
450+
var err error
451+
452+
clientset, dynamicClient, nsSelClient, err = setupClusterClients()
453+
if err != nil {
454+
return nil, err
455+
}
456+
457+
nsSelReconciler = common.NewNamespaceSelectorReconciler(nsSelClient, nsSelUpdatesChan)
458+
} else {
459+
dynamicClient = dynfake.NewSimpleDynamicClient(scheme.Scheme)
460+
clientset = clientsetfake.NewSimpleClientset()
461+
nsSelReconciler = common.NewNamespaceSelectorReconciler(runtimeClient, nsSelUpdatesChan)
462+
}
463+
464+
watcherReconciler, _ := depclient.NewControllerRuntimeSource()
435465
dynamicWatcher := depclient.NewWithClients(
436466
dynamicClient,
437467
clientset.Discovery(),
@@ -446,14 +476,28 @@ func (d *DryRunner) setupReconciler(
446476
}
447477
}()
448478

449-
runtimeClient := clientfake.NewClientBuilder().
450-
WithScheme(scheme.Scheme).
451-
WithObjects(configPolCRD, cfgPolicy).
452-
WithStatusSubresource(cfgPolicy).
453-
Build()
479+
rec := ctrl.ConfigurationPolicyReconciler{
480+
Client: runtimeClient,
481+
DecryptionConcurrency: 1,
482+
DynamicWatcher: dynamicWatcher,
483+
Scheme: scheme.Scheme,
484+
Recorder: record.NewFakeRecorder(8),
485+
InstanceName: "policy-cli",
486+
TargetK8sClient: clientset,
487+
TargetK8sDynamicClient: dynamicClient,
488+
SelectorReconciler: &nsSelReconciler,
489+
EnableMetrics: false,
490+
UninstallMode: false,
491+
EvalBackoffSeconds: 5,
492+
FullDiffs: d.fullDiffs,
493+
}
454494

455-
nsSelUpdatesChan := make(chan event.GenericEvent, 20)
456-
nsSelReconciler := common.NewNamespaceSelectorReconciler(runtimeClient, nsSelUpdatesChan)
495+
// wait for dynamic watcher to have started
496+
<-rec.DynamicWatcher.Started()
497+
498+
if d.fromCluster {
499+
return &rec, nil
500+
}
457501

458502
defaultNs := &unstructured.Unstructured{
459503
Object: map[string]interface{}{
@@ -478,21 +522,7 @@ func (d *DryRunner) setupReconciler(
478522
return nil, err
479523
}
480524

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

497527
if d.mappingsPath != "" {
498528
mFile, err := os.ReadFile(d.mappingsPath)
@@ -505,19 +535,16 @@ func (d *DryRunner) setupReconciler(
505535
return nil, err
506536
}
507537

508-
clientset.Resources = mappings.ResourceLists(apiMappings)
538+
fakeClientset.Resources = mappings.ResourceLists(apiMappings)
509539
} else {
510-
clientset.Resources, err = mappings.DefaultResourceLists()
540+
fakeClientset.Resources, err = mappings.DefaultResourceLists()
511541
if err != nil {
512542
return nil, err
513543
}
514544
}
515545

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

522549
return &rec, nil
523550
}
@@ -633,6 +660,40 @@ func sanitizeForCreation(obj *unstructured.Unstructured) {
633660
delete(obj.Object["metadata"].(map[string]interface{}), "uid")
634661
}
635662

663+
func setupClusterClients() (kubernetes.Interface, dynamic.Interface, client.Client, error) {
664+
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
665+
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
666+
loadingRules, &clientcmd.ConfigOverrides{},
667+
)
668+
669+
kubeConfig, err := clientConfig.ClientConfig()
670+
if err != nil {
671+
return nil, nil, nil, err
672+
}
673+
674+
clientset, err := kubernetes.NewForConfig(kubeConfig)
675+
if err != nil {
676+
return nil, nil, nil, err
677+
}
678+
679+
dynamicClient, err := dynamic.NewForConfig(kubeConfig)
680+
if err != nil {
681+
return nil, nil, nil, err
682+
}
683+
684+
readOnlyMode := true // Prevent modifications to the cluster
685+
686+
runtimeClient, err := client.New(kubeConfig, client.Options{
687+
Scheme: scheme.Scheme,
688+
DryRun: &readOnlyMode,
689+
})
690+
if err != nil {
691+
return nil, nil, nil, err
692+
}
693+
694+
return clientset, dynamicClient, runtimeClient, nil
695+
}
696+
636697
func addSupportedResources(clientset *clientsetfake.Clientset) {
637698
clientset.Resources = append(clientset.Resources, &metav1.APIResourceList{
638699
GroupVersion: parentpolicyv1.GroupVersion.String(),

0 commit comments

Comments
 (0)