Skip to content

Commit 43b5215

Browse files
authored
Merge pull request #257 from seans3/server-side-apply
Implements server-side apply
2 parents 6bac388 + 9928537 commit 43b5215

18 files changed

+229
-32
lines changed

cmd/apply/cmdapply.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,15 @@ func GetApplyRunner(provider provider.Provider, ioStreams genericclioptions.IOSt
3535
RunE: r.RunE,
3636
}
3737

38+
cmd.Flags().BoolVar(&r.serverSideOptions.ServerSideApply, "server-side", false,
39+
"If true, apply merge patch is calculated on API server instead of client.")
40+
cmd.Flags().BoolVar(&r.serverSideOptions.ForceConflicts, "force-conflicts", false,
41+
"If true, overwrite applied fields on server if field manager conflict.")
42+
cmd.Flags().StringVar(&r.serverSideOptions.FieldManager, "field-manager", common.DefaultFieldManager,
43+
"The client owner of the fields being applied on the server-side.")
44+
3845
cmd.Flags().StringVar(&r.output, "output", printers.DefaultPrinter(),
3946
fmt.Sprintf("Output format, must be one of %s", strings.Join(printers.SupportedPrinters(), ",")))
40-
4147
cmd.Flags().DurationVar(&r.period, "poll-period", 2*time.Second,
4248
"Polling period for resource statuses.")
4349
cmd.Flags().DurationVar(&r.reconcileTimeout, "reconcile-timeout", time.Duration(0),
@@ -64,6 +70,7 @@ type ApplyRunner struct {
6470
Applier *apply.Applier
6571
provider provider.Provider
6672

73+
serverSideOptions common.ServerSideOptions
6774
output string
6875
period time.Duration
6976
reconcileTimeout time.Duration
@@ -111,8 +118,9 @@ func (r *ApplyRunner) RunE(cmd *cobra.Command, args []string) error {
111118
return err
112119
}
113120
ch := r.Applier.Run(context.Background(), object.InfosToUnstructureds(infos), apply.Options{
114-
PollInterval: r.period,
115-
ReconcileTimeout: r.reconcileTimeout,
121+
ServerSideOptions: r.serverSideOptions,
122+
PollInterval: r.period,
123+
ReconcileTimeout: r.reconcileTimeout,
116124
// If we are not waiting for status, tell the applier to not
117125
// emit the events.
118126
EmitStatusEvents: emitStatusEvents,

cmd/preview/cmdpreview.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,16 @@ func (r *PreviewRunner) RunE(cmd *cobra.Command, args []string) error {
113113

114114
// Run the applier. It will return a channel where we can receive updates
115115
// to keep track of progress and any issues.
116+
serverSideOptions := common.ServerSideOptions{
117+
ServerSideApply: false,
118+
ForceConflicts: false,
119+
FieldManager: common.DefaultFieldManager,
120+
}
116121
ch = r.Applier.Run(ctx, object.InfosToUnstructureds(infos), apply.Options{
117-
EmitStatusEvents: false,
118-
NoPrune: noPrune,
119-
DryRunStrategy: drs,
122+
EmitStatusEvents: false,
123+
NoPrune: noPrune,
124+
DryRunStrategy: drs,
125+
ServerSideOptions: serverSideOptions,
120126
})
121127
} else {
122128
inv, _, err := inventory.SplitUnstructureds(object.InfosToUnstructureds(infos))
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
[kind]: https://github.com/kubernetes-sigs/kind
2+
3+
# Demo: Server Side Apply
4+
5+
This demo shows how to invoke server-side apply,
6+
instead of the default client-side apply.
7+
8+
First define a place to work:
9+
10+
<!-- @makeWorkplace @testE2EAgainstLatestRelease -->
11+
```
12+
DEMO_HOME=$(mktemp -d)
13+
```
14+
15+
Alternatively, use
16+
17+
> ```
18+
> DEMO_HOME=~/hello
19+
> ```
20+
21+
## Establish the base
22+
23+
<!-- @createBase @testE2EAgainstLatestRelease -->
24+
```
25+
BASE=$DEMO_HOME/base
26+
mkdir -p $BASE
27+
OUTPUT=$DEMO_HOME/output
28+
mkdir -p $OUTPUT
29+
30+
function expectedOutputLine() {
31+
test 1 == \
32+
$(grep "$@" $OUTPUT/status | wc -l); \
33+
echo $?
34+
}
35+
```
36+
37+
## Create the first "app"
38+
39+
Create the config yaml for two config maps: (cm-a, cm-b).
40+
41+
<!-- @createFirstConfigMaps @testE2EAgainstLatestRelease-->
42+
```
43+
cat <<EOF >$BASE/config-map-a.yaml
44+
apiVersion: v1
45+
kind: ConfigMap
46+
metadata:
47+
name: cm-a
48+
labels:
49+
name: test-config-map-label
50+
EOF
51+
52+
cat <<EOF >$BASE/config-map-b.yaml
53+
apiVersion: v1
54+
kind: ConfigMap
55+
metadata:
56+
name: cm-b
57+
labels:
58+
name: test-config-map-label
59+
data:
60+
foo: sean
61+
EOF
62+
```
63+
64+
## Run end-to-end tests
65+
66+
The following requires installation of [kind].
67+
68+
Delete any existing kind cluster and create a new one. By default the name of the cluster is "kind".
69+
70+
<!-- @deleteAndCreateKindCluster @testE2EAgainstLatestRelease -->
71+
```
72+
kind delete cluster
73+
kind create cluster
74+
```
75+
76+
Use the kapply init command to generate the inventory template. This contains
77+
the namespace and inventory id used by apply to create inventory objects.
78+
<!-- @createInventoryTemplate @testE2EAgainstLatestRelease-->
79+
```
80+
kapply init $BASE > $OUTPUT/status
81+
expectedOutputLine "namespace: default is used for inventory object"
82+
```
83+
84+
Apply the "app" to the cluster. All the config maps should be created, and
85+
no resources should be pruned.
86+
<!-- @runServerSideApply @testE2EAgainstLatestRelease -->
87+
```
88+
kapply apply $BASE --server-side --reconcile-timeout=1m > $OUTPUT/status
89+
expectedOutputLine "configmap/cm-a serversideapplied"
90+
expectedOutputLine "configmap/cm-b serversideapplied"
91+
expectedOutputLine "2 serverside applied"
92+
93+
# There should be only one inventory object
94+
kubectl get cm --selector='cli-utils.sigs.k8s.io/inventory-id' --no-headers | wc -l > $OUTPUT/status
95+
expectedOutputLine "1"
96+
# Capture the inventory object name for later testing
97+
kubectl get cm --selector='!cli-utils.sigs.k8s.io/inventory-id' --no-headers | wc -l > $OUTPUT/status
98+
expectedOutputLine "2"
99+
# ConfigMap cm-a had been created in the cluster
100+
kubectl get configmap/cm-a --no-headers | wc -l > $OUTPUT/status
101+
expectedOutputLine "1"
102+
# ConfigMap cm-b had been created in the cluster
103+
kubectl get configmap/cm-b --no-headers | wc -l > $OUTPUT/status
104+
expectedOutputLine "1"
105+
```
106+
107+
Update a config map to update a field owned by the default field manager.
108+
Update both config maps, using a different field-manager to create a
109+
conflict, but the the --force-conflicts flag to overwrite successfully.
110+
The conflicting field is "data.foo".
111+
<!-- @runServerSideApplyWithForceConflicts @testE2EAgainstLatestRelease -->
112+
```
113+
cat <<EOF >$BASE/config-map-b.yaml
114+
apiVersion: v1
115+
kind: ConfigMap
116+
metadata:
117+
name: cm-b
118+
labels:
119+
name: test-config-map-label
120+
data:
121+
foo: baz
122+
EOF
123+
124+
kapply apply $BASE --server-side --field-manager=sean --force-conflicts --reconcile-timeout=1m > $OUTPUT/status
125+
expectedOutputLine "configmap/cm-a serversideapplied"
126+
expectedOutputLine "configmap/cm-b serversideapplied"
127+
expectedOutputLine "2 serverside applied"
128+
```

pkg/apply/applier.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ func (a *Applier) Run(ctx context.Context, objects []*unstructured.Unstructured,
197197
InfoHelper: a.infoHelper,
198198
Mapper: mapper,
199199
}).BuildTaskQueue(resourceObjects, solver.Options{
200+
ServerSideOptions: options.ServerSideOptions,
200201
ReconcileTimeout: options.ReconcileTimeout,
201202
Prune: !options.NoPrune,
202203
DryRunStrategy: options.DryRunStrategy,
@@ -237,6 +238,9 @@ func (a *Applier) Run(ctx context.Context, objects []*unstructured.Unstructured,
237238
}
238239

239240
type Options struct {
241+
// Encapsulates the fields for server-side apply.
242+
ServerSideOptions common.ServerSideOptions
243+
240244
// ReconcileTimeout defines whether the applier should wait
241245
// until all applied resources have been reconciled, and if so,
242246
// how long to wait.

pkg/apply/event/applyeventoperation_string.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apply/event/applyeventtype_string.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apply/event/deleteeventoperation_string.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apply/event/deleteeventtype_string.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apply/event/pruneeventoperation_string.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apply/event/pruneeventtype_string.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)