Skip to content

Commit 6e91017

Browse files
committed
add recreate option for update-policy directive
1 parent 3ef9f8e commit 6e91017

File tree

5 files changed

+70
-13
lines changed

5 files changed

+70
-13
lines changed

internal/commands/apply.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ func doApply(args []string, config applyCommandConfig) error {
114114

115115
opts := config.syncOptions
116116
opts.DisableUpdateFn = newUpdatePolicy().disableUpdate
117+
opts.RecreateUpdateFn = newUpdatePolicy().recreateUpdate
117118

118119
if !opts.DryRun && len(objects) > 0 {
119120
msg := fmt.Sprintf("will synchronize %d object(s)", len(objects))

internal/commands/directives.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ import (
2626
)
2727

2828
const (
29-
policyNever = "never"
30-
policyDefault = "default"
29+
policyNever = "never"
30+
policyRecreate = "recreate"
31+
policyDefault = "default"
3132
)
3233

3334
// isSet return true if the annotation name specified as directive is equal to the supplied value.
@@ -64,6 +65,10 @@ func (u *updatePolicy) disableUpdate(ob model.K8sMeta) bool {
6465
return isSet(ob, model.QbecNames.Directives.UpdatePolicy, policyNever, []string{policyDefault})
6566
}
6667

68+
func (u *updatePolicy) recreateUpdate(ob model.K8sMeta) bool {
69+
return isSet(ob, model.QbecNames.Directives.UpdatePolicy, policyRecreate, []string{policyDefault})
70+
}
71+
6772
func newUpdatePolicy() *updatePolicy {
6873
return &updatePolicy{}
6974
}

internal/commands/directives_test.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func TestDirectivesIsSet(t *testing.T) {
8181
}
8282
}
8383

84-
func TestDirectivesUpdatePolicy(t *testing.T) {
84+
func TestDirectivesUpdatePolicyNever(t *testing.T) {
8585
up := newUpdatePolicy()
8686
a := assert.New(t)
8787
ret := up.disableUpdate(k8sMetaWithAnnotations("ConfigMap", "foo", "bar", nil))
@@ -92,6 +92,17 @@ func TestDirectivesUpdatePolicy(t *testing.T) {
9292
a.True(ret)
9393
}
9494

95+
func TestDirectivesUpdatePolicyRecreate(t *testing.T) {
96+
up := newUpdatePolicy()
97+
a := assert.New(t)
98+
ret := up.recreateUpdate(k8sMetaWithAnnotations("ConfigMap", "foo", "bar", nil))
99+
a.False(ret)
100+
ret = up.recreateUpdate(k8sMetaWithAnnotations("ConfigMap", "foo", "bar", map[string]interface{}{
101+
"directives.qbec.io/update-policy": "recreate",
102+
}))
103+
a.True(ret)
104+
}
105+
95106
func TestDirectivesDeletePolicy(t *testing.T) {
96107
dp := newDeletePolicy(func(gvk schema.GroupVersionKind) (bool, error) {
97108
return gvk.Kind == "ConfigMap", nil

internal/remote/client.go

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3535
"k8s.io/apimachinery/pkg/runtime/schema"
3636
apiTypes "k8s.io/apimachinery/pkg/types"
37+
"k8s.io/apimachinery/pkg/watch"
3738
"k8s.io/client-go/discovery"
3839
"k8s.io/client-go/dynamic"
3940
)
@@ -64,11 +65,12 @@ type ConditionFunc func(obj model.K8sMeta) bool
6465

6566
// SyncOptions provides the caller with options for the sync operation.
6667
type SyncOptions struct {
67-
DryRun bool // do not actually create or update objects, return what would happen
68-
DisableCreate bool // only update objects if they exist, do not create new ones
69-
DisableUpdateFn ConditionFunc // do not update an existing object
70-
WaitOptions TypeWaitOptions // opts for waiting
71-
ShowSecrets bool // show secrets in patches and creations
68+
DryRun bool // do not actually create or update objects, return what would happen
69+
DisableCreate bool // only update objects if they exist, do not create new ones
70+
DisableUpdateFn ConditionFunc // do not update an existing object
71+
RecreateUpdateFn ConditionFunc // recreate existing object on update
72+
WaitOptions TypeWaitOptions // opts for waiting
73+
ShowSecrets bool // show secrets in patches and creations
7274
}
7375

7476
// DeleteOptions provides the caller with options for the delete operation.
@@ -647,6 +649,37 @@ func (c *Client) maybeCreate(obj model.K8sLocalObject, opts SyncOptions) (*updat
647649
return result, nil
648650
}
649651

652+
func (c *Client) doRecreate(obj model.K8sLocalObject, opts SyncOptions) (*updateResult, error) {
653+
ri, err := c.resourceInterfaceWithDefaultNs(obj.GroupVersionKind(), obj.GetNamespace())
654+
if err != nil {
655+
return nil, errors.Wrap(err, "get resource interface")
656+
}
657+
658+
sio.Debugln("delete " + c.DisplayName(obj))
659+
pp := metav1.DeletePropagationForeground
660+
err = ri.Delete(obj.GetName(), &metav1.DeleteOptions{PropagationPolicy: &pp})
661+
if err != nil && !apiErrors.IsNotFound(err) {
662+
return nil, err
663+
}
664+
665+
watcher, err := ri.Watch(metav1.ListOptions{
666+
FieldSelector: "metadata.name=" + obj.GetName(),
667+
})
668+
if err != nil {
669+
return nil, err
670+
}
671+
672+
sio.Debugln("wait " + c.DisplayName(obj))
673+
for {
674+
ev := <-watcher.ResultChan()
675+
if ev.Type == watch.Deleted {
676+
break
677+
}
678+
}
679+
watcher.Stop()
680+
return c.maybeCreate(obj, opts)
681+
}
682+
650683
func (c *Client) maybeUpdate(obj model.K8sLocalObject, remObj *unstructured.Unstructured, opts SyncOptions) (*updateResult, error) {
651684
if opts.DisableUpdateFn(model.NewK8sObject(remObj.Object)) {
652685
return &updateResult{
@@ -686,10 +719,16 @@ func (c *Client) maybeUpdate(obj model.K8sLocalObject, remObj *unstructured.Unst
686719
}
687720

688721
var result *updateResult
689-
if opts.DryRun {
690-
result, err = p.getPatchContents(remObj, obj)
691-
} else {
692-
result, err = p.patch(remObj, obj)
722+
723+
patch, err := p.getPatchContents(remObj, obj)
724+
if err != nil || opts.DryRun {
725+
return patch, err
693726
}
727+
if patch.SkipReason != identicalObjects && opts.RecreateUpdateFn(model.NewK8sObject(remObj.Object)) {
728+
return c.doRecreate(obj, opts)
729+
}
730+
731+
result, err = p.patch(remObj, obj)
732+
694733
return result, err
695734
}

site/content/reference/directives.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ object to remove this annotation will not work.
2626
#### `directives.qbec.io/update-policy`
2727

2828
* Annotation source: in-cluster object.
29-
* Allowed values: `"default"`, `"never"`
29+
* Allowed values: `"default"`, `"never"`, `"recreate"`
3030
* Default value: `"default"`
3131

3232
when set to `"never"`, indicates that the specific object should never be updated.
33+
when set to `"recreate"`, indicates that the specific object should be recreated instad of updating.
3334
If you want qbec to update this object, you need to remove the annotation from the in-cluster object. Changing the source
3435
object to remove this annotation will not work.
3536

0 commit comments

Comments
 (0)