Skip to content

Commit 0a47c21

Browse files
Microzuul CIGerrit Code Review
authored andcommitted
Merge "feat(cli): Add --dry-run flag to deploy command"
2 parents e9bc87e + 9df8a61 commit 0a47c21

File tree

17 files changed

+317
-53
lines changed

17 files changed

+317
-53
lines changed

cli/cmd/dev/apply.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
"sigs.k8s.io/yaml"
3030
)
3131

32-
func ApplyStandalone(ns string, sfResource string, kubeContext string) {
32+
func ApplyStandalone(ns string, sfResource string, kubeContext string, dryRun bool) {
3333
if sfResource != "" && ns != "" {
3434
var sf sfv1.SoftwareFactory
3535
dat, err := os.ReadFile(sfResource)
@@ -45,7 +45,7 @@ func ApplyStandalone(ns string, sfResource string, kubeContext string) {
4545
"CR", sf,
4646
"CR name", sf.ObjectMeta.Name,
4747
"Namespace", ns)
48-
err = controllers.Standalone(sf, ns, kubeContext)
48+
err = controllers.Standalone(sf, ns, kubeContext, dryRun)
4949
if err != nil {
5050
ctrl.Log.Error(err, "Could not reconcile resource")
5151
os.Exit(1)

controllers/config.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ func (r *SFController) SetupBaseSecrets(internalTenantSecretsVersion string) boo
9292
var secret apiv1.Secret
9393
secretName := "config-update-secrets"
9494
if !r.GetM(secretName, &secret) {
95-
logging.LogI("Creating the config-update service account secret")
95+
if !r.DryRun {
96+
logging.LogI("Creating the config-update service account secret")
97+
}
9698
secret = apiv1.Secret{
9799
Type: "kubernetes.io/service-account-token",
98100
ObjectMeta: metav1.ObjectMeta{
@@ -130,7 +132,9 @@ func (r *SFController) SetupBaseSecrets(internalTenantSecretsVersion string) boo
130132
}
131133

132134
if !found {
133-
logging.LogI("Creating base secret job")
135+
if !r.DryRun {
136+
logging.LogI("Creating base secret job")
137+
}
134138
r.CreateR(r.RunCommand(jobName, []string{"config-create-zuul-secrets"}, extraCmdVars))
135139
return false
136140
} else if job.Status.Succeeded >= 1 {

controllers/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func Main(ns string, metricsAddr string, probeAddr string, enableLeaderElection
143143
}
144144
}
145145

146-
func Standalone(sf sfv1.SoftwareFactory, ns string, kubeContext string) error {
146+
func Standalone(sf sfv1.SoftwareFactory, ns string, kubeContext string, dryRun bool) error {
147147

148148
config := GetConfigContextOrDie(kubeContext)
149149
cl, err := client.New(config, client.Options{
@@ -161,6 +161,7 @@ func Standalone(sf sfv1.SoftwareFactory, ns string, kubeContext string) error {
161161
RESTClient: restClient,
162162
RESTConfig: config,
163163
CancelFunc: cancelFunc,
164+
DryRun: dryRun,
164165
}
165166
return sfr.StandaloneReconcile(ctx, ns, sf)
166167
}

controllers/softwarefactory_controller.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type SoftwareFactoryReconciler struct {
5252
RESTConfig *rest.Config
5353
CancelFunc context.CancelFunc
5454
Completed bool
55+
DryRun bool
5556
}
5657

5758
// Run `make manifests` to apply rbac change
@@ -589,6 +590,7 @@ func (r *SoftwareFactoryReconciler) mkSFController(
589590
owner: owner,
590591
standalone: standalone,
591592
zkChanged: false,
593+
DryRun: r.DryRun,
592594
},
593595
cr: cr,
594596
isOpenShift: CheckOpenShift(r.RESTConfig),
@@ -669,7 +671,9 @@ func (r *SoftwareFactoryReconciler) StandaloneReconcile(ctx context.Context, ns
669671
controllerCM.Data = map[string]string{
670672
"spec": string(marshaledSpec),
671673
}
672-
logging.LogI("Creating ConfigMap, name: " + controllerCMName)
674+
if !r.DryRun {
675+
logging.LogI("Creating ConfigMap, name: " + controllerCMName)
676+
}
673677
// Create the fake controller configMap
674678
if err := r.Create(ctx, &controllerCM); err != nil {
675679
log.Error(err, "Unable to create configMap", "name", controllerCMName)
@@ -681,6 +685,10 @@ func (r *SoftwareFactoryReconciler) StandaloneReconcile(ctx context.Context, ns
681685

682686
for {
683687
status := sfCtrl.Step()
688+
if r.DryRun {
689+
log.Info("[Dry Run] Standalone reconcile done.")
690+
return nil
691+
}
684692
attempt += 1
685693
if attempt == maxAttempt {
686694
return errors.New("unable to reconcile after max attempts")

controllers/utils.go

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"strings"
2020

2121
"sigs.k8s.io/controller-runtime/pkg/client"
22+
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
2223
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
2324

2425
"k8s.io/client-go/kubernetes"
@@ -67,6 +68,7 @@ type SFUtilContext struct {
6768
owner client.Object
6869
standalone bool
6970
zkChanged bool
71+
DryRun bool
7072
}
7173

7274
type HostAlias struct {
@@ -99,7 +101,8 @@ func (r *SFUtilContext) GetM(name string, obj client.Object) bool {
99101
client.ObjectKey{
100102
Name: name,
101103
Namespace: r.ns,
102-
}, obj)
104+
},
105+
obj)
103106
if errors.IsNotFound(err) {
104107
return false
105108
} else if err != nil {
@@ -110,6 +113,10 @@ func (r *SFUtilContext) GetM(name string, obj client.Object) bool {
110113

111114
// CreateR creates a resource with the owner as the ownerReferences.
112115
func (r *SFUtilContext) CreateR(obj client.Object) {
116+
if r.DryRun {
117+
logging.LogI("[Dry Run] Would create object, name: " + obj.GetName())
118+
return
119+
}
113120
r.setOwnerReference(obj)
114121
if err := r.Client.Create(r.ctx, obj); err != nil && !errors.IsAlreadyExists(err) {
115122
panic(err.Error())
@@ -118,13 +125,34 @@ func (r *SFUtilContext) CreateR(obj client.Object) {
118125

119126
// DeleteR delete a resource.
120127
func (r *SFUtilContext) DeleteR(obj client.Object) {
128+
if r.DryRun {
129+
logging.LogI("[Dry Run] Would delete object, name: " + obj.GetName())
130+
return
131+
}
121132
if err := r.Client.Delete(r.ctx, obj); err != nil && !errors.IsNotFound(err) {
122133
panic(err.Error())
123134
}
124135
}
125136

126137
// UpdateR updates resource with the owner as the ownerReferences.
127138
func (r *SFUtilContext) UpdateR(obj client.Object) bool {
139+
if r.DryRun {
140+
// When dry-running, we must check that the object exists to avoid reconciliation loops.
141+
gvk, err := apiutil.GVKForObject(obj, r.Scheme)
142+
if err != nil {
143+
panic(err.Error())
144+
}
145+
newObj, err := r.Scheme.New(gvk)
146+
if err != nil {
147+
// This should not happen if GVKForObject succeeded.
148+
panic(err.Error())
149+
}
150+
if !r.GetM(obj.GetName(), newObj.(client.Object)) {
151+
logging.LogI("[Dry Run] Would have failed to update non-existent object, name: " + obj.GetName())
152+
return false
153+
}
154+
return true
155+
}
128156
r.setOwnerReference(obj)
129157
logging.LogI("Updating object name:" + obj.GetName())
130158
if err := r.Client.Update(r.ctx, obj); err != nil {
@@ -140,7 +168,9 @@ func (r *SFUtilContext) GetOrCreate(obj client.Object) bool {
140168
name := obj.GetName()
141169

142170
if !r.GetM(name, obj) {
143-
logging.LogI("Creating object, name: " + obj.GetName())
171+
if !r.DryRun {
172+
logging.LogI("Creating object, name: " + obj.GetName())
173+
}
144174
r.CreateR(obj)
145175
return false
146176
}
@@ -191,15 +221,21 @@ func (r *SFUtilContext) EnsureConfigMap(baseName string, data map[string]string)
191221
name := baseName + "-config-map"
192222
var cm apiv1.ConfigMap
193223
if !r.GetM(name, &cm) {
194-
logging.LogI("Creating config map name: " + name)
224+
if !r.DryRun {
225+
logging.LogI("Creating config map name: " + name)
226+
}
195227
cm = apiv1.ConfigMap{
196228
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: r.ns},
197229
Data: data,
198230
}
199231
r.CreateR(&cm)
200232
} else {
201233
if !reflect.DeepEqual(cm.Data, data) {
202-
logging.LogI("Updating configmap, name: " + name)
234+
if r.DryRun {
235+
logging.LogI("[Dry Run] Would update ConfigMap, name: " + name + ". Reason: Data has changed.")
236+
} else {
237+
logging.LogI("Updating configmap, name: " + name)
238+
}
203239
cm.Data = data
204240
r.UpdateR(&cm)
205241
}
@@ -213,11 +249,17 @@ func (r *SFUtilContext) EnsureSecret(secret *apiv1.Secret) {
213249
var current apiv1.Secret
214250
name := secret.GetName()
215251
if !r.GetM(name, &current) {
216-
logging.LogI("Creating secret, name: " + name)
252+
if !r.DryRun {
253+
logging.LogI("Creating secret, name: " + name)
254+
}
217255
r.CreateR(secret)
218256
} else {
219257
if !reflect.DeepEqual(current.Data, secret.Data) {
220-
logging.LogI("Updating secret, name: " + name)
258+
if r.DryRun {
259+
logging.LogI("[Dry Run] Would update Secret, name: " + name + ". Reason: Data has changed.")
260+
} else {
261+
logging.LogI("Updating secret, name: " + name)
262+
}
221263
current.Data = secret.Data
222264
r.UpdateR(&current)
223265
}
@@ -231,7 +273,9 @@ func (r *SFUtilContext) EnsureSecret(secret *apiv1.Secret) {
231273
func (r *SFUtilContext) ensureSecretFromFunc(name string, getData func() string) apiv1.Secret {
232274
var secret apiv1.Secret
233275
if !r.GetM(name, &secret) {
234-
logging.LogI("Creating secret, name: " + name)
276+
if !r.DryRun {
277+
logging.LogI("Creating secret, name: " + name)
278+
}
235279
secret = base.MkSecretFromFunc(name, r.ns, getData)
236280
r.CreateR(&secret)
237281
}
@@ -250,7 +294,9 @@ func (r *SFUtilContext) EnsureSecretUUID(name string) apiv1.Secret {
250294
func (r *SFUtilContext) EnsureSSHKeySecret(name string) {
251295
var secret apiv1.Secret
252296
if !r.GetM(name, &secret) {
253-
logging.LogI("Creating ssh key, name: " + name)
297+
if !r.DryRun {
298+
logging.LogI("Creating ssh key, name: " + name)
299+
}
254300
secret := base.MkSSHKeySecret(name, r.ns)
255301
r.CreateR(&secret)
256302
}
@@ -270,12 +316,18 @@ func (r *SFUtilContext) EnsureService(service *apiv1.Service) {
270316
}
271317
name := service.GetName()
272318
if !r.GetM(name, &current) {
273-
logging.LogI("Creating service, name: " + name)
319+
if !r.DryRun {
320+
logging.LogI("Creating service, name: " + name)
321+
}
274322
r.CreateR(service)
275323
} else {
276324
if !reflect.DeepEqual(current.Spec.Selector, service.Spec.Selector) ||
277325
spsAsString(current.Spec.Ports) != spsAsString(service.Spec.Ports) {
278-
logging.LogI("Updating service, name: " + name)
326+
if r.DryRun {
327+
logging.LogI("[Dry Run] Would update Service, name: " + name + ". Reason: Spec has changed.")
328+
} else {
329+
logging.LogI("Updating service, name: " + name)
330+
}
279331
current.Spec = *service.Spec.DeepCopy()
280332
r.UpdateR(&current)
281333
}
@@ -384,6 +436,10 @@ func (r *SFUtilContext) getPods(dep *appsv1.StatefulSet) ([]apiv1.Pod, bool) {
384436

385437
// IsStatefulSetReady checks if StatefulSet is ready
386438
func (r *SFUtilContext) IsStatefulSetReady(dep *appsv1.StatefulSet) bool {
439+
if r.DryRun {
440+
return true
441+
}
442+
387443
logging.LogI("Waiting for statefulset, name: " + dep.ObjectMeta.GetName())
388444
rolloutOk := base.IsStatefulSetRolloutDone(dep)
389445
if rolloutOk {
@@ -407,6 +463,10 @@ func (r *SFUtilContext) IsStatefulSetReady(dep *appsv1.StatefulSet) bool {
407463

408464
// IsDeploymentReady checks if Deployment is ready
409465
func (r *SFUtilContext) IsDeploymentReady(dep *appsv1.Deployment) bool {
466+
if r.DryRun {
467+
return true
468+
}
469+
410470
logging.LogI("Waiting for deployment, name: " + dep.ObjectMeta.GetName())
411471
rolloutOk := base.IsDeploymentRolloutDone(dep)
412472
if rolloutOk {
@@ -646,7 +706,9 @@ func (r *SFController) EnsureDiskUsagePromRule(ruleGroups []monitoringv1.RuleGro
646706
return false
647707
} else {
648708
if !utils.MapEquals(&currentPromRule.ObjectMeta.Annotations, &desiredDUPromRule.ObjectMeta.Annotations) {
649-
logging.LogI("Default disk usage Prometheus rules changed, updating...")
709+
if !r.DryRun {
710+
logging.LogI("Default disk usage Prometheus rules changed, updating...")
711+
}
650712
currentPromRule.Spec = desiredDUPromRule.Spec
651713
currentPromRule.ObjectMeta.Annotations = desiredDUPromRule.ObjectMeta.Annotations
652714
r.UpdateR(&currentPromRule)
@@ -676,7 +738,9 @@ func (r *SFController) EnsureSFPodMonitor(ports []string, selector metav1.LabelS
676738
return false
677739
} else {
678740
if !utils.MapEquals(&currentPodMonitor.ObjectMeta.Annotations, &annotations) {
679-
logging.LogI("SF PodMonitor configuration changed, updating...")
741+
if !r.DryRun {
742+
logging.LogI("SF PodMonitor configuration changed, updating...")
743+
}
680744
currentPodMonitor.Spec = desiredPodMonitor.Spec
681745
currentPodMonitor.ObjectMeta.Annotations = annotations
682746
r.UpdateR(&currentPodMonitor)
@@ -793,7 +857,11 @@ func (r *SFController) ensureDeployment(dep appsv1.Deployment) (*appsv1.Deployme
793857
needUpdate = true
794858
}
795859
if needUpdate {
796-
logging.LogI(name + " configuration changed, rollout pods ...")
860+
if r.DryRun {
861+
logging.LogI("[Dry Run] Would update Deployment, name: " + name + ". Reason: Spec template has changed.")
862+
} else {
863+
logging.LogI(name + " configuration changed, rollout pods ...")
864+
}
797865
current.Spec = dep.DeepCopy().Spec
798866
r.UpdateR(&current)
799867
return &current, true
@@ -825,7 +893,11 @@ func (r *SFController) ensureStatefulset(storageClass *string, sts appsv1.Statef
825893
needUpdate = true
826894
}
827895
if needUpdate {
828-
logging.LogI(name + " configuration changed, rollout pods ...")
896+
if r.DryRun {
897+
logging.LogI("[Dry Run] Would update StatefulSet, name: " + name + ". Reason: Spec template has changed.")
898+
} else {
899+
logging.LogI(name + " configuration changed, rollout pods ...")
900+
}
829901
r.UpdateR(&current)
830902
return &current, true
831903
}

doc/reference/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ All notable changes to this project will be documented in this file.
1919
- standalone mode: the sf-standalone-owner configMap is annotated with the CLI's version that
2020
deployed the resource, and the end time of the deployment. The configMap's data
2121
is also set to hold the last applied SoftwareFactory spec.
22+
- Add a `--dry-run` flag to the `deploy` command. When used, the operator will log the actions it would take without
23+
performing them, preventing any resource creation or modification.
2224

2325
### Changed
2426

main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
"errors"
2727
)
2828

29+
var dryRun bool
30+
2931
// getWatchNamespace returns the Namespace the operator should be watching for changes
3032
func getWatchNamespace() (string, error) {
3133
// WatchNamespaceEnvVar is the constant for env variable WATCH_NAMESPACE
@@ -83,7 +85,7 @@ func deployCmd(kmd *cobra.Command, args []string) {
8385
ctrl.Log.Error(err, "Argument error:")
8486
os.Exit(1)
8587
}
86-
dev.ApplyStandalone(ns, sfResource, kubeContext)
88+
dev.ApplyStandalone(ns, sfResource, kubeContext, dryRun)
8789
}
8890

8991
func main() {
@@ -118,6 +120,9 @@ func main() {
118120
}
119121
)
120122

123+
// Flags for the deploy command
124+
deployCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Shows what resources will be changed by a deploy operation")
125+
121126
// Global flags
122127
rootCmd.PersistentFlags().StringVarP(&ns, "namespace", "n", "", "The namespace on which to perform actions.")
123128
rootCmd.PersistentFlags().StringVarP(&kubeContext, "kube-context", "k", "", "The cluster context to use to perform calls to the K8s API.")

0 commit comments

Comments
 (0)