Skip to content

Commit

Permalink
Migrate to server-side applies by default
Browse files Browse the repository at this point in the history
  • Loading branch information
pst committed Dec 17, 2024
1 parent adf37d2 commit 5d5c82e
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 19 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ terraform.d
crash.log
dist/
kustomize/test_kustomizations/helm/remote/
.vscode/
9 changes: 9 additions & 0 deletions kustomize/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,15 @@ func (km *kManifest) apiDelete(opts k8smetav1.DeleteOptions) (err error) {
return api.Delete(context.TODO(), km.name(), opts)
}

func (km *kManifest) apiApply(opts k8smetav1.ApplyOptions) (resp *k8sunstructured.Unstructured, err error) {
api, err := km.api()
if err != nil {
return resp, km.fmtErr(fmt.Errorf("create failed: %s", err))
}

return api.Apply(context.TODO(), km.name(), km.resource, opts)
}

func (km *kManifest) apiPreparePatch(kmo *kManifest, currAllowNotFound bool) (pt k8stypes.PatchType, p []byte, err error) {
original := kmo.json
modified := km.json
Expand Down
10 changes: 9 additions & 1 deletion kustomize/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Config struct {
Mapper *restmapper.DeferredDiscoveryRESTMapper
Mutex *sync.Mutex
GzipLastAppliedConfig bool
ServerSideApply bool
}

// Provider ...
Expand Down Expand Up @@ -75,6 +76,12 @@ func Provider() *schema.Provider {
Default: true,
Description: "When 'true' compress the lastAppliedConfig annotation for resources that otherwise would exceed K8s' max annotation size. All other resources use the regular uncompressed annotation. Set to 'false' to disable compression entirely.",
},
"server_side_apply": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "When 'true' use server-side apply.",
},
},
}

Expand Down Expand Up @@ -142,8 +149,9 @@ func Provider() *schema.Provider {
mu := &sync.Mutex{}

gzipLastAppliedConfig := d.Get("gzip_last_applied_config").(bool)
serverSideApply := d.Get("server_side_apply").(bool)

return &Config{client, mapper, mu, gzipLastAppliedConfig}, nil
return &Config{client, mapper, mu, gzipLastAppliedConfig, serverSideApply}, nil
}

return p
Expand Down
94 changes: 78 additions & 16 deletions kustomize/resource_kustomization.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ import (
k8serrors "k8s.io/apimachinery/pkg/api/errors"
k8smeta "k8s.io/apimachinery/pkg/api/meta"
k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)

const fieldManager string = "terraform-provider-kustomization"

func kustomizationResource() *schema.Resource {
return &schema.Resource{
Create: kustomizationResourceCreate,
Expand Down Expand Up @@ -53,6 +57,9 @@ func kustomizationResource() *schema.Resource {
func kustomizationResourceCreate(d *schema.ResourceData, m interface{}) error {
mapper := m.(*Config).Mapper
client := m.(*Config).Client
gzipLastAppliedConfig := m.(*Config).GzipLastAppliedConfig
serverSideApply := m.(*Config).ServerSideApply

km := newKManifest(mapper, client)

err := km.load([]byte(d.Get("manifest").(string)))
Expand Down Expand Up @@ -101,12 +108,24 @@ func kustomizationResourceCreate(d *schema.ResourceData, m interface{}) error {
}
}

gzipLastAppliedConfig := m.(*Config).GzipLastAppliedConfig
setLastAppliedConfig(km, gzipLastAppliedConfig)

resp, err := km.apiCreate(k8smetav1.CreateOptions{})
if err != nil {
return logError(err)
var resp *unstructured.Unstructured
if serverSideApply {
resp, err = km.apiApply(k8smetav1.ApplyOptions{
FieldManager: fieldManager,
Force: true,
})
}

// if server-side apply disabled or failed, fallback to create
if !serverSideApply || err != nil {
resp, err = km.apiCreate(k8smetav1.CreateOptions{
FieldManager: fieldManager,
})
if err != nil {
return logError(fmt.Errorf("create failed: %s", err))
}
}

if d.Get("wait").(bool) {
Expand Down Expand Up @@ -181,6 +200,7 @@ func kustomizationResourceDiff(ctx context.Context, d *schema.ResourceDiff, m in
client := m.(*Config).Client
mapper := m.(*Config).Mapper
gzipLastAppliedConfig := m.(*Config).GzipLastAppliedConfig
serverSideApply := m.(*Config).ServerSideApply

do, dm := d.GetChange("manifest")

Expand Down Expand Up @@ -213,7 +233,19 @@ func kustomizationResourceDiff(ctx context.Context, d *schema.ResourceDiff, m in

if do.(string) == "" {
// diffing for create
_, err = kmm.apiCreate(k8smetav1.CreateOptions{DryRun: []string{k8smetav1.DryRunAll}})
if serverSideApply {
_, err = kmm.apiApply(k8smetav1.ApplyOptions{
FieldManager: fieldManager,
Force: true,
})
}

// if server-side apply disabled or failed, fallback to create
if !serverSideApply || err != nil {
_, err = kmm.apiCreate(k8smetav1.CreateOptions{
FieldManager: fieldManager,
})
}
if err != nil {
if k8serrors.IsAlreadyExists(err) {
// this is an edge case during tests
Expand Down Expand Up @@ -249,14 +281,28 @@ func kustomizationResourceDiff(ctx context.Context, d *schema.ResourceDiff, m in
return nil
}

pt, p, err := kmm.apiPreparePatch(kmo, true)
if err != nil {
return logError(err)
if serverSideApply {
_, err = kmm.apiApply(k8smetav1.ApplyOptions{
DryRun: []string{k8smetav1.DryRunAll},
FieldManager: fieldManager,
Force: true,
})
}

dryRunPatch := k8smetav1.PatchOptions{DryRun: []string{k8smetav1.DryRunAll}}
// if server-side apply disabled or failed, fallback to patch
if !serverSideApply {
pt, p, pErr := kmm.apiPreparePatch(kmo, true)
if pErr != nil {
return logError(err)
}

_, err = kmm.apiPatch(pt, p, k8smetav1.PatchOptions{
DryRun: []string{k8smetav1.DryRunAll},
FieldManager: fieldManager,
Force: ptr.To(true),
})
}

_, err = kmm.apiPatch(pt, p, dryRunPatch)
if err != nil {
// Handle specific invalid errors
if k8serrors.IsInvalid(err) {
Expand Down Expand Up @@ -305,6 +351,7 @@ func kustomizationResourceUpdate(d *schema.ResourceData, m interface{}) error {
client := m.(*Config).Client
mapper := m.(*Config).Mapper
gzipLastAppliedConfig := m.(*Config).GzipLastAppliedConfig
serverSideApply := m.(*Config).ServerSideApply

do, dm := d.GetChange("manifest")

Expand All @@ -329,14 +376,29 @@ func kustomizationResourceUpdate(d *schema.ResourceData, m interface{}) error {
setLastAppliedConfig(kmo, gzipLastAppliedConfig)
setLastAppliedConfig(kmm, gzipLastAppliedConfig)

pt, p, err := kmm.apiPreparePatch(kmo, false)
if err != nil {
return logError(err)
var resp *unstructured.Unstructured
if serverSideApply {
resp, err = kmm.apiApply(k8smetav1.ApplyOptions{
DryRun: []string{k8smetav1.DryRunAll},
FieldManager: fieldManager,
Force: true,
})
}

resp, err := kmm.apiPatch(pt, p, k8smetav1.PatchOptions{})
if err != nil {
return logError(err)
// if server-side apply disabled or failed, fallback to patch
if !serverSideApply || err != nil {
pt, p, err := kmm.apiPreparePatch(kmo, true)
if err != nil {
return logError(err)
}

resp, err = kmm.apiPatch(pt, p, k8smetav1.PatchOptions{
FieldManager: fieldManager,
})

if err != nil {
return logError(err)
}
}

if d.Get("wait").(bool) {
Expand Down
21 changes: 19 additions & 2 deletions kustomize/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import (
k8sruntime "k8s.io/apimachinery/pkg/runtime"
k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
k8stypes "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/structured-merge-diff/v4/typed"

"k8s.io/apimachinery/pkg/util/jsonmergepatch"
"k8s.io/apimachinery/pkg/util/managedfields"
"k8s.io/apimachinery/pkg/util/mergepatch"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/kubectl/pkg/scheme"
Expand Down Expand Up @@ -61,9 +63,24 @@ func setLastAppliedConfig(km *kManifest, gzipLastAppliedConfig bool) {
}

func getLastAppliedConfig(u *k8sunstructured.Unstructured, gzipLastAppliedConfig bool) (lac string) {
ac := &k8sunstructured.Unstructured{}
managedfields.ExtractInto(u, typed.DeducedParseableType, fieldManager, ac, "")

if ac.GetNamespace() == u.GetNamespace() && ac.GetName() == u.GetName() {
b, err := ac.MarshalJSON()
if err != nil {
log.Fatalln(err)
}

return string(b)
}

annotations := u.GetAnnotations()

lac = u.GetAnnotations()[lastAppliedConfigAnnotation]
lac, ok := annotations[lastAppliedConfigAnnotation]
if !ok {
lac = "{}"
}

if gzipLastAppliedConfig {
// read the compressed lac if available
Expand Down Expand Up @@ -110,7 +127,7 @@ func getPatch(gvk k8sschema.GroupVersionKind, original []byte, modified []byte,
}
case err != nil:
return pt, p, fmt.Errorf("getPatch failed: %s", err)
case err == nil:
default:
pt = k8stypes.StrategicMergePatchType

lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject)
Expand Down

0 comments on commit 5d5c82e

Please sign in to comment.