Skip to content

✨ (go/v4): Add support to Server-Side Apply#5458

Open
camilamacedo86 wants to merge 1 commit into
kubernetes-sigs:masterfrom
camilamacedo86:server-side-apply-plugin
Open

✨ (go/v4): Add support to Server-Side Apply#5458
camilamacedo86 wants to merge 1 commit into
kubernetes-sigs:masterfrom
camilamacedo86:server-side-apply-plugin

Conversation

@camilamacedo86
Copy link
Copy Markdown
Member

@camilamacedo86 camilamacedo86 commented Feb 8, 2026

This PR adds a new ssa/v1-alpha plugin that scaffolds APIs with controllers using Kubernetes Server-Side Apply (SSA) patterns. How that works: See the doc added: https://deploy-preview-5458--kubebuilder.netlify.app/reference/server-side-apply.html

What is it?

Server-Side Apply helps when multiple actors (your controller, users, other controllers) need to manage different fields of the same resource. Instead of overwriting the entire object, SSA tracks field ownership and only updates the fields you explicitly manage.

Usage

# Initialize project                                      
kubebuilder init --domain example.com
                                                                                                
# Create API with SSA plugin
kubebuilder create api \                                                                        
  --group apps \                                                                                
  --version v1 \                                                                                
  --kind Application \                                                                          
  --ssa"                                                        
                                                                                                
# Generate apply configurations
make generate                                                                                   

What it scaffolds

  1. API types with SSA markers - adds +genclient and +kubebuilder:ac:generate=true markers
  2. Makefile updates - adds applyconfiguration generation (first time only)

After running make generate, you get type-safe apply configuration helpers:

  // Build desired state - only specify fields you want to manage                                 
  appApply := appsv1apply.Application(name, namespace).                                           
      WithSpec(appsv1apply.ApplicationSpec().                                                     
          WithReplicas(3))                                                                        
                                                                                                 
  // Apply - preserves fields managed by users/other controllers                                  
  r.Patch(ctx, app, client.Apply, client.ForceOwnership,                                          
      client.FieldOwner("my-controller"))                                                         

Closes: #2514
Closes: #5237

@k8s-ci-robot k8s-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Feb 8, 2026
@k8s-ci-robot k8s-ci-robot added approved Indicates a PR has been approved by an approver from all required OWNERS files. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Feb 8, 2026
@camilamacedo86 camilamacedo86 force-pushed the server-side-apply-plugin branch from 0d291e1 to 60416fd Compare February 26, 2026 15:28
@k8s-ci-robot k8s-ci-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Feb 28, 2026
@camilamacedo86 camilamacedo86 force-pushed the server-side-apply-plugin branch from 60416fd to d24e315 Compare March 12, 2026 06:11
@k8s-ci-robot k8s-ci-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Mar 12, 2026
@camilamacedo86 camilamacedo86 force-pushed the server-side-apply-plugin branch 8 times, most recently from 10a43a3 to 0725248 Compare March 13, 2026 03:58
@camilamacedo86 camilamacedo86 changed the title WIP feat(server-side-apply/v1-alpha): Add plugin to scaffold APIs with Server-Side Apply feat(ssa/v1-alpha): Add plugin to scaffold APIs with Server-Side Apply Mar 13, 2026
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 13, 2026
@camilamacedo86 camilamacedo86 changed the title feat(ssa/v1-alpha): Add plugin to scaffold APIs with Server-Side Apply ✨ feat(ssa/v1-alpha): Add plugin to scaffold APIs with Server-Side Apply Mar 13, 2026
@camilamacedo86 camilamacedo86 changed the title ✨ feat(ssa/v1-alpha): Add plugin to scaffold APIs with Server-Side Apply ✨ feat(ssa/v1-alpha): Add plugin to scaffold APIs with Server-Side Apply Mar 13, 2026
@camilamacedo86 camilamacedo86 changed the title ✨ feat(ssa/v1-alpha): Add plugin to scaffold APIs with Server-Side Apply WIP ✨ feat(ssa/v1-alpha): Add plugin to scaffold APIs with Server-Side Apply Mar 13, 2026
@k8s-ci-robot k8s-ci-robot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 13, 2026
@camilamacedo86 camilamacedo86 force-pushed the server-side-apply-plugin branch 4 times, most recently from 971b7d3 to fd92b49 Compare March 13, 2026 05:16
@camilamacedo86 camilamacedo86 changed the title WIP ✨ feat(ssa/v1-alpha): Add plugin to scaffold APIs with Server-Side Apply ✨ feat(ssa/v1-alpha): Add plugin to scaffold APIs with Server-Side Apply Mar 13, 2026
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 13, 2026

// Apply - only manages the fields you specified above
// User's custom labels/annotations are preserved!
if err := r.Patch(ctx, resource, client.Apply, client.ForceOwnership,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my own culture, is proning client.ForceOwnership a result of "a single resource should not be managed by multiple operator" logic ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Good catcher !!!

@camilamacedo86 camilamacedo86 force-pushed the server-side-apply-plugin branch from 7bba449 to b4130bd Compare May 1, 2026 06:11
@k8s-ci-robot k8s-ci-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 1, 2026
@camilamacedo86 camilamacedo86 force-pushed the server-side-apply-plugin branch from b4130bd to 00e30de Compare May 1, 2026 07:02
@camilamacedo86 camilamacedo86 requested a review from Copilot May 1, 2026 07:39

This comment was marked as resolved.

@camilamacedo86

This comment was marked as resolved.

@k8s-ci-robot k8s-ci-robot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label May 1, 2026

This comment was marked as resolved.

@camilamacedo86 camilamacedo86 force-pushed the server-side-apply-plugin branch from b206c73 to 6fb93ef Compare May 1, 2026 08:16
@camilamacedo86 camilamacedo86 changed the title ✨ feat(ssa/v1-alpha): Add plugin to scaffold APIs with Server-Side Apply WIP ✨ feat(ssa/v1-alpha): Add plugin to scaffold APIs with Server-Side Apply May 1, 2026
@k8s-ci-robot k8s-ci-robot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label May 1, 2026

This comment was marked as outdated.

Comment on lines +14 to +20
Use this flag when:

- **Multiple controllers manage the same resource**: Your controller manages some fields while other controllers or users manage others
- **Users customize your CRs**: Users add their own labels, annotations, or spec fields that your controller shouldn't overwrite
- **Partial field management**: You only want to manage specific fields and leave others alone
- **Avoiding conflicts**: You want declarative field ownership tracking to prevent accidental overwrites

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure on how to phrase it, but should we also say "Expect other people to automate your CRs through higher level operators" ?

As in the operator that has generated SSA types would not be the one using them but merely providing them for other operators to import ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown

@yyewolf yyewolf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a really nice improvement, I can't wait to try it and we (at my company) will have a lot of work to do to use this as effectively as possible 🔥

}

// Apply using SSA - only manages the fields specified above
if err := r.Patch(ctx, resource, client.Apply,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stumbled on this PR while searching for recommendations on SSA for kubebuilder controllers. looks like client.Apply is deprecated. Is this still the way to do SSA?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

.PHONY: generate
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
"$(CONTROLLER_GEN)" object:headerFile="hack/boilerplate.go.txt",year=$(YEAR) paths="./..."
"$(CONTROLLER_GEN)" object:headerFile="hack/boilerplate.go.txt",year=$(YEAR) applyconfiguration:headerFile="hack/boilerplate.go.txt" paths="./..."
Copy link
Copy Markdown
Member Author

@camilamacedo86 camilamacedo86 May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoelSpeed @alvaroaleman @sbueringer
It would be great if we could not need to add at all here the applyconfiguration since it will only generate the resources when the ac marker is used and a project can have CRDs that are SSA and others that not.

The need to add this does not bring a nice UX.
Could we not change the ssa marker / generator to work as any other marker in the controller-tools?

- [Kubernetes Server-Side Apply Documentation][server-side-apply]
- [controller-gen CLI Reference](./controller-gen.md)

[server-side-apply]: https://kubernetes.io/docs/reference/using-api/server-side-apply/
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoelSpeed @alvaroaleman @sbueringer

Could you please give a hand on the review of this doc?
Could you please let us know if has any info here that is not accurate or can/should be shaped?

Comment thread pkg/plugins/golang/v4/scaffolds/api.go Outdated
// Try multiple patterns to handle different Makefile formats
// 1. Try with boilerplate and YEAR variable (current default)
//nolint:lll
err = util.ReplaceInFile(makefilePath, makefileOldObjectGenWithBoilerplateAndYear, makefileNewObjectGenWithBoilerplateAndYear)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, but the nested fallback structure is a little hard to scan. Since the intent is “try these known Makefile formats in order and succeed on the first match,” I think this could be made table-driven.

Something like:

func replaceObjectGenInMakefile(makefilePath string) error {
	replacements := []struct {
		old string
		new string
	}{
		{
			old: makefileOldObjectGenWithBoilerplateAndYear,
			new: makefileNewObjectGenWithBoilerplateAndYear,
		},
		{
			old: makefileOldObjectGenWithBoilerplate,
			new: makefileNewObjectGenWithBoilerplate,
		},
		{
			old: makefileOldObjectGenNoBoilerplate,
			new: makefileNewObjectGenNoBoilerplate,
		},
	}
	for _, replacement := range replacements {
		if err := util.ReplaceInFile(makefilePath, replacement.old, replacement.new); err == nil {
			return nil
		}
	}
	return fmt.Errorf("none of the known controller-gen object generator patterns matched")
}

Then the caller can stay simple:

if err := replaceObjectGenInMakefile(makefilePath); err != nil {
	log.Warn("unable to find standard controller-gen object generator line in Makefile. "+
		"Add applyconfiguration generation manually",
		"error", err)
	return
}

This avoids the nested if err != nil chain and removes the need for //nolint:lll.
Small note, it assumes ReplaceInFile returning an error means the pattern did not match in this context. If it can also fail because of read or write errors, it may be worth distinguishing those cases separately.

"--group", kbc.Group,
"--version", kbc.Version,
"--kind", kbc.Kind,
"--namespaced",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can SSA also be cluster-scoped?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes work with both.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the test exists only for namespaced, but no test for non-nemaspaced.

@v47
Copy link
Copy Markdown
Contributor

v47 commented May 12, 2026

Since SSA ownership is only claimed when the user actually calls the Apply with a field owner, maybe the flag description/docs should avoid saying the scaffolded controller uses SSA. It enables SSA support, but does not claim ownership until user code calls something like:

r.Client.Status().Apply(ctx, desired, client.FieldOwner("foo-controller"))

makefilePath := "Makefile"

// Skip if already updated
hasMarker, err := util.HasFileContentWith(makefilePath, makefileApplyConfigurationMarker)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just my thought

I was thinking would it be better than we return error from here on the reason why the updateMakefile failed and then callee here will handle the error and mention explicitly with log.Warn(err, "Failed to automatically update Makefile"). I feel this would make it much cleaner based on the logs on what exactly the issue is.

Note: We can still not fail the scaffolding process and let it continue

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not return an error because that would means fail when the Makefile is customized and does not allow move forward if the end user change their Makefile.

If we be unable to update the Makefile we warn and then end users are aware of to update it manually

Comment on lines +247 to +248
func (s *apiScaffolder) updateMakefile() {
makefilePath := "Makefile"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I was wondering, do we want to add a troubeshooting/manual steps if automatic update of Makefile failed. Currently we are just logging with warning messages.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We warn and end users can manually update their project.
It is too simple, has not really a reason for troubeshooting.
What we can do is provide a better warn message that can be more helpful.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 🚀

Add --ssa flag to create api command for scaffolding APIs with Server-Side
Apply support. Uses template conditions instead of code injection where possible.
@k8s-ci-robot
Copy link
Copy Markdown
Contributor

@camilamacedo86: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
pull-kubebuilder-e2e-k8s-1-33-0 7bba449 link true /test pull-kubebuilder-e2e-k8s-1-33-0
pull-kubebuilder-e2e-k8s-1-36-0 7afc07d link true /test pull-kubebuilder-e2e-k8s-1-36-0
pull-kubebuilder-e2e-k8s-1-35-0 7afc07d link true /test pull-kubebuilder-e2e-k8s-1-35-0
pull-kubebuilder-e2e-k8s-1-34-0 7afc07d link true /test pull-kubebuilder-e2e-k8s-1-34-0

Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Copy link
Copy Markdown
Contributor

@mayuka-c mayuka-c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@k8s-ci-robot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: camilamacedo86, mayuka-c, yyewolf

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Server Side Apply documentation to the book

8 participants