Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.

Commit 96f9727

Browse files
author
Wendy Maria Arango Chavarria
authored
feat: Add --update-strategy flag to change apply behavior when contents from file is applied (#402)
- This is an initial commit, the first part that is creating the flag, allow autocompletion and adding the warning message for this new breaking change. - Added fields validation (usage of --filepath with --update-strategy and `merge`, `replace` as only accepted values for --update-strategy) - Add unit tests for fields validation - Add logic for flag behavior - Amend the workload assignation when replacing - Add --update-strategy to options when updating workload from file Signed-off-by: Wendy Arango <warango@vmware.com>
1 parent 0c5f3b2 commit 96f9727

32 files changed

Lines changed: 2623 additions & 46 deletions

docs/command-reference/tanzu_apps_workload_apply.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ tanzu apps workload apply --file workload.yaml
6363
--tail show logs while waiting for workload to become ready
6464
--tail-timestamp show logs and add timestamp to each log line while waiting for workload to become ready
6565
-t, --type type distinguish workload type
66+
--update-strategy string specify configuration file update strategy (supported strategies: merge, replace) (default "merge")
6667
--wait waits for workload to become ready
6768
--wait-timeout duration timeout for workload to become ready when waiting (default 10m0s)
6869
-y, --yes accept all prompts

docs/command-reference/tanzu_apps_workload_tail.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ tanzu apps workload tail my-workload --since 1h
2525
--component name workload component name (e.g. build)
2626
-h, --help help for tail
2727
-n, --namespace name kubernetes namespace (defaulted from kube config)
28-
--since duration time duration to start reading logs from (default 1s)
28+
--since duration time duration to start reading logs from (default 1m0s)
2929
-t, --timestamp print timestamp for each log line
3030
```
3131

docs/commands-details/workload_create_update_apply.md

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -398,36 +398,23 @@ Create workload:
398398
</details>
399399

400400
### `--image`, `-i`
401-
Sets the OSI image to be used as the workload application source instead of a git repository
401+
Sets the OCI image to be used as the workload application source instead of a git repository
402402

403403
<details><summary>Example</summary>
404404

405405
```bash
406406
tanzu apps workload apply spring-pet-clinic --image private.repo.domain.com/spring-pet-clinic --type web
407407
Create workload:
408-
1 + |---
409-
2 + |apiVersion: carto.run/v1alpha1
410-
3 + |kind: Workload
411-
4 + |metadata:
412-
5 + | labels:
413-
6 + | apps.tanzu.vmware.com/workload-type: web
414-
7 + | name: spring-pet-clinic
415-
8 + | namespace: default
416-
9 + |spec:
417-
10 + | build:
418-
11 + | env:
419-
12 + | - name: JAVA_VERSION
420-
13 + | value: "1.8"
421-
14 + | params:
422-
15 + | - name: server
423-
16 + | value:
424-
17 + | management-port: 9190
425-
18 + | port: 9090
426-
19 + | source:
427-
20 + | git:
428-
21 + | ref:
429-
22 + | tag: tap-1.1
430-
23 + | url: https://github.com/sample-accelerators/spring-petclinic
408+
1 + |---
409+
2 + |apiVersion: carto.run/v1alpha1
410+
3 + |kind: Workload
411+
4 + |metadata:
412+
5 + | labels:
413+
6 + | apps.tanzu.vmware.com/workload-type: web
414+
7 + | name: spring-pet-clinic
415+
8 + | namespace: default
416+
9 + |spec:
417+
10 + | image: private.repo.domain.com/spring-pet-clinic
431418

432419
? Do you want to create this workload? [yN]
433420
```

pkg/apis/cartographer/v1alpha1/workload_helpers.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"encoding/json"
2222
"fmt"
2323
"io"
24+
"reflect"
2425

2526
corev1 "k8s.io/api/core/v1"
2627
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -97,6 +98,20 @@ func (w *WorkloadSpec) MergeServiceAccountName(serviceAccountName string) {
9798
w.ServiceAccountName = serviceAccountNamePtr
9899
}
99100

101+
func (w *Workload) ReplaceMetadata(updates *Workload) {
102+
if updates != nil && !reflect.DeepEqual(updates.ObjectMeta, (metav1.ObjectMeta{})) {
103+
wldMeta := w.GetObjectMeta()
104+
updatesMeta := updates.GetObjectMeta()
105+
106+
// assign the system populated fields to the new workload
107+
wldMeta.SetResourceVersion(updatesMeta.GetResourceVersion())
108+
wldMeta.SetUID(updatesMeta.GetUID())
109+
wldMeta.SetGeneration(updatesMeta.GetGeneration())
110+
wldMeta.SetCreationTimestamp(updatesMeta.GetCreationTimestamp())
111+
wldMeta.SetDeletionTimestamp(updatesMeta.GetDeletionTimestamp())
112+
}
113+
}
114+
100115
func (w *Workload) Merge(updates *Workload) {
101116
for k, v := range updates.Annotations {
102117
w.MergeAnnotations(k, v)

pkg/apis/cartographer/v1alpha1/workload_test.go

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ import (
2121
"fmt"
2222
"os"
2323
"testing"
24+
"time"
2425

2526
"github.com/google/go-cmp/cmp"
2627
corev1 "k8s.io/api/core/v1"
2728
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2829
"k8s.io/apimachinery/pkg/api/resource"
2930
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/apimachinery/pkg/types"
3032

3133
"github.com/vmware-tanzu/apps-cli-plugin/pkg/apis"
3234
cli "github.com/vmware-tanzu/apps-cli-plugin/pkg/cli-runtime"
@@ -190,6 +192,255 @@ func TestWorkload_MergeServiceAccountName(t *testing.T) {
190192
}
191193
}
192194

195+
func TestWorkload_ReplaceMetadata(t *testing.T) {
196+
tests := []struct {
197+
name string
198+
seed *Workload
199+
update *Workload
200+
want *Workload
201+
}{{
202+
name: "empty",
203+
seed: &Workload{},
204+
update: &Workload{},
205+
want: &Workload{},
206+
}, {
207+
name: "add all metadata to new workload",
208+
seed: &Workload{
209+
ObjectMeta: metav1.ObjectMeta{
210+
Name: "my-workload",
211+
Namespace: "default",
212+
Annotations: map[string]string{
213+
"name": "value",
214+
},
215+
Labels: map[string]string{
216+
"name": "value",
217+
},
218+
Finalizers: []string{"my.finalizer"},
219+
DeletionGracePeriodSeconds: &[]int64{5}[0],
220+
ManagedFields: []metav1.ManagedFieldsEntry{
221+
{Manager: "tanzu"},
222+
},
223+
SelfLink: "/default/my-workload",
224+
OwnerReferences: []metav1.OwnerReference{
225+
{
226+
APIVersion: "v1",
227+
Kind: "Pod",
228+
Name: "workload-owner",
229+
},
230+
},
231+
},
232+
},
233+
update: &Workload{
234+
ObjectMeta: metav1.ObjectMeta{
235+
UID: types.UID("uid-xyz"),
236+
ResourceVersion: "999",
237+
Generation: 1,
238+
CreationTimestamp: metav1.Date(2019, time.September, 10, 15, 00, 00, 00, time.UTC),
239+
DeletionTimestamp: &metav1.Time{Time: time.Date(2021, 6, 29, 01, 44, 05, 0, time.UTC)},
240+
},
241+
},
242+
want: &Workload{
243+
ObjectMeta: metav1.ObjectMeta{
244+
Name: "my-workload",
245+
Namespace: "default",
246+
Annotations: map[string]string{
247+
"name": "value",
248+
},
249+
Labels: map[string]string{
250+
"name": "value",
251+
},
252+
UID: types.UID("uid-xyz"),
253+
ResourceVersion: "999",
254+
Generation: 1,
255+
CreationTimestamp: metav1.Date(2019, time.September, 10, 15, 00, 00, 00, time.UTC),
256+
DeletionTimestamp: &metav1.Time{Time: time.Date(2021, 6, 29, 01, 44, 05, 0, time.UTC)},
257+
Finalizers: []string{"my.finalizer"},
258+
DeletionGracePeriodSeconds: &[]int64{5}[0],
259+
ManagedFields: []metav1.ManagedFieldsEntry{
260+
{Manager: "tanzu"},
261+
},
262+
SelfLink: "/default/my-workload",
263+
OwnerReferences: []metav1.OwnerReference{
264+
{
265+
APIVersion: "v1",
266+
Kind: "Pod",
267+
Name: "workload-owner",
268+
},
269+
},
270+
},
271+
},
272+
}, {
273+
name: "overwrite workload metadata",
274+
seed: &Workload{
275+
ObjectMeta: metav1.ObjectMeta{
276+
Name: "my-workload",
277+
Namespace: "default",
278+
Annotations: map[string]string{
279+
"name": "value",
280+
},
281+
Labels: map[string]string{
282+
"name": "value",
283+
},
284+
UID: types.UID("uid-xyz"),
285+
ResourceVersion: "111",
286+
Generation: 1,
287+
CreationTimestamp: metav1.Date(2018, time.September, 10, 15, 00, 00, 00, time.UTC),
288+
DeletionTimestamp: &metav1.Time{Time: time.Date(2020, 6, 29, 01, 44, 05, 0, time.UTC)},
289+
Finalizers: []string{"my.finalizer"},
290+
DeletionGracePeriodSeconds: &[]int64{5}[0],
291+
ManagedFields: []metav1.ManagedFieldsEntry{
292+
{Manager: "tanzu"},
293+
},
294+
SelfLink: "/default/my-workload",
295+
OwnerReferences: []metav1.OwnerReference{
296+
{
297+
APIVersion: "v1",
298+
Kind: "Pod",
299+
Name: "workload-owner",
300+
},
301+
},
302+
},
303+
},
304+
update: &Workload{
305+
ObjectMeta: metav1.ObjectMeta{
306+
UID: types.UID("uid-xyz"),
307+
ResourceVersion: "999",
308+
Generation: 1,
309+
CreationTimestamp: metav1.Date(2019, time.September, 10, 15, 00, 00, 00, time.UTC),
310+
DeletionTimestamp: &metav1.Time{Time: time.Date(2021, 6, 29, 01, 44, 05, 0, time.UTC)},
311+
},
312+
},
313+
want: &Workload{
314+
ObjectMeta: metav1.ObjectMeta{
315+
Name: "my-workload",
316+
Namespace: "default",
317+
Annotations: map[string]string{
318+
"name": "value",
319+
},
320+
Labels: map[string]string{
321+
"name": "value",
322+
},
323+
UID: types.UID("uid-xyz"),
324+
ResourceVersion: "999",
325+
Generation: 1,
326+
CreationTimestamp: metav1.Date(2019, time.September, 10, 15, 00, 00, 00, time.UTC),
327+
DeletionTimestamp: &metav1.Time{Time: time.Date(2021, 6, 29, 01, 44, 05, 0, time.UTC)},
328+
Finalizers: []string{"my.finalizer"},
329+
DeletionGracePeriodSeconds: &[]int64{5}[0],
330+
ManagedFields: []metav1.ManagedFieldsEntry{
331+
{Manager: "tanzu"},
332+
},
333+
SelfLink: "/default/my-workload",
334+
OwnerReferences: []metav1.OwnerReference{
335+
{
336+
APIVersion: "v1",
337+
Kind: "Pod",
338+
Name: "workload-owner",
339+
},
340+
},
341+
},
342+
},
343+
}, {
344+
name: "do not update to empty metadata",
345+
seed: &Workload{
346+
ObjectMeta: metav1.ObjectMeta{
347+
Name: "my-workload",
348+
Namespace: "default",
349+
Annotations: map[string]string{
350+
"name": "value",
351+
},
352+
Labels: map[string]string{
353+
"name": "value",
354+
},
355+
UID: types.UID("uid-xyz"),
356+
ResourceVersion: "999",
357+
Generation: 1,
358+
CreationTimestamp: metav1.Date(2019, time.September, 10, 15, 00, 00, 00, time.UTC),
359+
DeletionTimestamp: &metav1.Time{Time: time.Date(2021, 6, 29, 01, 44, 05, 0, time.UTC)},
360+
Finalizers: []string{"my.finalizer"},
361+
DeletionGracePeriodSeconds: &[]int64{5}[0],
362+
ManagedFields: []metav1.ManagedFieldsEntry{
363+
{Manager: "tanzu"},
364+
},
365+
SelfLink: "/default/my-workload",
366+
OwnerReferences: []metav1.OwnerReference{
367+
{
368+
APIVersion: "v1",
369+
Kind: "Pod",
370+
Name: "workload-owner",
371+
},
372+
},
373+
},
374+
},
375+
update: &Workload{
376+
ObjectMeta: metav1.ObjectMeta{},
377+
},
378+
want: &Workload{
379+
ObjectMeta: metav1.ObjectMeta{
380+
Name: "my-workload",
381+
Namespace: "default",
382+
Annotations: map[string]string{
383+
"name": "value",
384+
},
385+
Labels: map[string]string{
386+
"name": "value",
387+
},
388+
UID: types.UID("uid-xyz"),
389+
ResourceVersion: "999",
390+
Generation: 1,
391+
CreationTimestamp: metav1.Date(2019, time.September, 10, 15, 00, 00, 00, time.UTC),
392+
DeletionTimestamp: &metav1.Time{Time: time.Date(2021, 6, 29, 01, 44, 05, 0, time.UTC)},
393+
Finalizers: []string{"my.finalizer"},
394+
DeletionGracePeriodSeconds: &[]int64{5}[0],
395+
ManagedFields: []metav1.ManagedFieldsEntry{
396+
{Manager: "tanzu"},
397+
},
398+
SelfLink: "/default/my-workload",
399+
OwnerReferences: []metav1.OwnerReference{
400+
{
401+
APIVersion: "v1",
402+
Kind: "Pod",
403+
Name: "workload-owner",
404+
},
405+
},
406+
},
407+
},
408+
}, {
409+
name: "return only updated system populated fields",
410+
seed: &Workload{
411+
ObjectMeta: metav1.ObjectMeta{},
412+
},
413+
update: &Workload{
414+
ObjectMeta: metav1.ObjectMeta{
415+
UID: types.UID("uid-xyz"),
416+
ResourceVersion: "999",
417+
Generation: 1,
418+
CreationTimestamp: metav1.Date(2019, time.September, 10, 15, 00, 00, 00, time.UTC),
419+
DeletionTimestamp: &metav1.Time{Time: time.Date(2021, 6, 29, 01, 44, 05, 0, time.UTC)},
420+
},
421+
},
422+
want: &Workload{
423+
ObjectMeta: metav1.ObjectMeta{
424+
UID: types.UID("uid-xyz"),
425+
ResourceVersion: "999",
426+
Generation: 1,
427+
CreationTimestamp: metav1.Date(2019, time.September, 10, 15, 00, 00, 00, time.UTC),
428+
DeletionTimestamp: &metav1.Time{Time: time.Date(2021, 6, 29, 01, 44, 05, 0, time.UTC)},
429+
},
430+
},
431+
}}
432+
433+
for _, test := range tests {
434+
t.Run(test.name, func(t *testing.T) {
435+
got := test.seed.DeepCopy()
436+
got.ReplaceMetadata(test.update)
437+
if diff := cmp.Diff(test.want, got); diff != "" {
438+
t.Errorf("Merge() (-want, +got) = %v", diff)
439+
}
440+
})
441+
}
442+
}
443+
193444
func TestWorkload_Validate(t *testing.T) {
194445
tests := []struct {
195446
name string

pkg/cli-runtime/testing/command_table.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,11 @@ type CommandTestCase struct {
163163
// lifecycle
164164

165165
// Prepare is called before the command is executed. It is intended to prepare that broader
166-
// environment before the specific table record is executed. For example, chaning the working
166+
// environment before the specific table record is executed. For example, changing the working
167167
// directory or setting mock expectations.
168168
Prepare func(t *testing.T, ctx context.Context, config *cli.Config, tc *CommandTestCase) (context.Context, error)
169169
// CleanUp is called after the table record is finished and all defined assertions complete.
170-
// It is indended to clean up any state created in the Prepare step or during the test
170+
// It is intended to clean up any state created in the Prepare step or during the test
171171
// execution, or to make assertions for mocks.
172172
CleanUp func(t *testing.T, ctx context.Context, config *cli.Config, tc *CommandTestCase) error
173173
// WithConsoleInteractions receives function with an expect.Console that can be used to send characters and verify the output send to a fake console.

0 commit comments

Comments
 (0)