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 18, 2024
1 parent adf37d2 commit f680060
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 50 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/
29 changes: 20 additions & 9 deletions kustomize/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import (
k8serrors "k8s.io/apimachinery/pkg/api/errors"
k8smeta "k8s.io/apimachinery/pkg/api/meta"
k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

k8sunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
k8stypes "k8s.io/apimachinery/pkg/types"
k8smetav1ac "k8s.io/client-go/applyconfigurations/meta/v1"
k8sdynamic "k8s.io/client-go/dynamic"
"k8s.io/client-go/restmapper"
)
Expand Down Expand Up @@ -85,16 +85,18 @@ func emptyToUnderscore(value string) string {
}

type kManifest struct {
resource *k8sunstructured.Unstructured
mapper *restmapper.DeferredDiscoveryRESTMapper
client k8sdynamic.Interface
json []byte
resource *k8sunstructured.Unstructured
mapper *restmapper.DeferredDiscoveryRESTMapper
extractor k8smetav1ac.UnstructuredExtractor
client k8sdynamic.Interface
json []byte
}

func newKManifest(mapper *restmapper.DeferredDiscoveryRESTMapper, client k8sdynamic.Interface) *kManifest {
func newKManifest(m *restmapper.DeferredDiscoveryRESTMapper, c k8sdynamic.Interface, e k8smetav1ac.UnstructuredExtractor) *kManifest {
return &kManifest{
mapper: mapper,
client: client,
mapper: m,
client: c,
extractor: e,
}
}

Expand Down Expand Up @@ -204,6 +206,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 Expand Up @@ -253,7 +264,7 @@ func (km *kManifest) getNamespaceManifest() (kns *kManifest, namespaced bool) {
return kns, false
}

kns = newKManifest(km.mapper, km.client)
kns = newKManifest(km.mapper, km.client, km.extractor)

kns.resource = kns.resource.NewEmptyInstance().(*k8sunstructured.Unstructured)

Expand Down
30 changes: 27 additions & 3 deletions kustomize/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

metav1ac "k8s.io/client-go/applyconfigurations/meta/v1"
"k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/memory"
"k8s.io/client-go/dynamic"
Expand All @@ -22,8 +23,10 @@ import (
type Config struct {
Client dynamic.Interface
Mapper *restmapper.DeferredDiscoveryRESTMapper
Extractor metav1ac.UnstructuredExtractor
Mutex *sync.Mutex
GzipLastAppliedConfig bool
ServerSideApply bool
}

// Provider ...
Expand Down Expand Up @@ -75,6 +78,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 @@ -134,16 +143,31 @@ func Provider() *schema.Provider {
return nil, fmt.Errorf("provider kustomization: %s", err)
}

mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(dc))
cDc := memory.NewMemCacheClient(dc)

mapper := restmapper.NewDeferredDiscoveryRESTMapper(cDc)

extractor, err := metav1ac.NewUnstructuredExtractor(cDc)
if err != nil {
return nil, fmt.Errorf("provider kustomization: %s", err)
}

// Mutex to prevent parallel Kustomizer runs
// temp workaround for upstream bug
// https://github.com/kubernetes-sigs/kustomize/issues/3659
mu := &sync.Mutex{}

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

return &Config{client, mapper, mu, gzipLastAppliedConfig}, nil
serverSideApply := d.Get("server_side_apply").(bool)

return &Config{
Client: client,
Mapper: mapper,
Extractor: extractor,
Mutex: mu,
GzipLastAppliedConfig: gzipLastAppliedConfig,
ServerSideApply: serverSideApply,
}, nil
}

return p
Expand Down
129 changes: 93 additions & 36 deletions kustomize/resource_kustomization.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ 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"
)

const fieldManager string = "terraform-provider-kustomization"

func kustomizationResource() *schema.Resource {
return &schema.Resource{
Create: kustomizationResourceCreate,
Expand Down Expand Up @@ -53,7 +56,11 @@ func kustomizationResource() *schema.Resource {
func kustomizationResourceCreate(d *schema.ResourceData, m interface{}) error {
mapper := m.(*Config).Mapper
client := m.(*Config).Client
km := newKManifest(mapper, client)
extractor := m.(*Config).Extractor
gzipLastAppliedConfig := m.(*Config).GzipLastAppliedConfig
serverSideApply := m.(*Config).ServerSideApply

km := newKManifest(mapper, client, extractor)

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

gzipLastAppliedConfig := m.(*Config).GzipLastAppliedConfig
setLastAppliedConfig(km, gzipLastAppliedConfig)
var resp *unstructured.Unstructured

resp, err := km.apiCreate(k8smetav1.CreateOptions{})
if err != nil {
return logError(err)
if serverSideApply {
resp, err = km.apiApply(k8smetav1.ApplyOptions{
FieldManager: fieldManager,
Force: true,
})
if err != nil {
return logError(fmt.Errorf("server-side apply error: %s", err))
}
}

// if server-side apply disabled or failed, fallback to create
if !serverSideApply {
setLastAppliedConfig(km, gzipLastAppliedConfig)

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 All @@ -118,13 +141,19 @@ func kustomizationResourceCreate(d *schema.ResourceData, m interface{}) error {
id := string(resp.GetUID())
d.SetId(id)

d.Set("manifest", getLastAppliedConfig(resp, gzipLastAppliedConfig))
lac := extractLastAppliedConfig(resp, extractor, gzipLastAppliedConfig)
d.Set("manifest", lac)

return kustomizationResourceRead(d, m)
}

func kustomizationResourceRead(d *schema.ResourceData, m interface{}) error {
km := newKManifest(m.(*Config).Mapper, m.(*Config).Client)
client := m.(*Config).Client
mapper := m.(*Config).Mapper
extractor := m.(*Config).Extractor
gzipLastAppliedConfig := m.(*Config).GzipLastAppliedConfig

km := newKManifest(mapper, client, extractor)

err := km.load([]byte(d.Get("manifest").(string)))
if err != nil {
Expand All @@ -139,13 +168,18 @@ func kustomizationResourceRead(d *schema.ResourceData, m interface{}) error {
id := string(resp.GetUID())
d.SetId(id)

d.Set("manifest", getLastAppliedConfig(resp, m.(*Config).GzipLastAppliedConfig))
lac := extractLastAppliedConfig(resp, extractor, gzipLastAppliedConfig)
d.Set("manifest", lac)

return nil
}

func kustomizationResourceExists(d *schema.ResourceData, m interface{}) (bool, error) {
km := newKManifest(m.(*Config).Mapper, m.(*Config).Client)
client := m.(*Config).Client
mapper := m.(*Config).Mapper
extractor := m.(*Config).Extractor

km := newKManifest(mapper, client, extractor)

err := km.load([]byte(d.Get("manifest").(string)))
if err != nil {
Expand Down Expand Up @@ -180,11 +214,12 @@ func kustomizationResourceDiff(ctx context.Context, d *schema.ResourceDiff, m in

client := m.(*Config).Client
mapper := m.(*Config).Mapper
extractor := m.(*Config).Extractor
gzipLastAppliedConfig := m.(*Config).GzipLastAppliedConfig

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

kmm := newKManifest(mapper, client)
kmm := newKManifest(mapper, client, extractor)
err := kmm.load([]byte(dm.(string)))
if err != nil {
return logError(err)
Expand Down Expand Up @@ -212,8 +247,10 @@ 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}})
_, err = kmm.apiCreate(k8smetav1.CreateOptions{
FieldManager: fieldManager,
})

if err != nil {
if k8serrors.IsAlreadyExists(err) {
// this is an edge case during tests
Expand All @@ -235,8 +272,7 @@ func kustomizationResourceDiff(ctx context.Context, d *schema.ResourceDiff, m in
return nil
}

// diffing for update
kmo := newKManifest(mapper, client)
kmo := newKManifest(mapper, client, extractor)
err = kmo.load([]byte(do.(string)))
if err != nil {
return logError(err)
Expand All @@ -254,9 +290,11 @@ func kustomizationResourceDiff(ctx context.Context, d *schema.ResourceDiff, m in
return logError(err)
}

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

_, err = kmm.apiPatch(pt, p, dryRunPatch)
if err != nil {
// Handle specific invalid errors
if k8serrors.IsInvalid(err) {
Expand Down Expand Up @@ -304,39 +342,56 @@ func kustomizationResourceDiff(ctx context.Context, d *schema.ResourceDiff, m in
func kustomizationResourceUpdate(d *schema.ResourceData, m interface{}) error {
client := m.(*Config).Client
mapper := m.(*Config).Mapper
extractor := m.(*Config).Extractor
gzipLastAppliedConfig := m.(*Config).GzipLastAppliedConfig
serverSideApply := m.(*Config).ServerSideApply

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

kmo := newKManifest(mapper, client)
err := kmo.load([]byte(do.(string)))
if err != nil {
return logError(err)
}

kmm := newKManifest(mapper, client)
err = kmm.load([]byte(dm.(string)))
kmm := newKManifest(mapper, client, extractor)
err := kmm.load([]byte(dm.(string)))
if err != nil {
return logError(err)
}
setLastAppliedConfig(kmm, gzipLastAppliedConfig)

if !d.HasChange("manifest") && !d.HasChange("wait") {
return logError(kmm.fmtErr(
errors.New("update called without diff"),
))
}

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{
FieldManager: fieldManager,
Force: true,
})
if err != nil {
logError(fmt.Errorf("server-side apply error: %s", err))
}
}

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 {
kmo := newKManifest(mapper, client, extractor)
err = kmo.load([]byte(do.(string)))
if err != nil {
return logError(err)
}
setLastAppliedConfig(kmo, gzipLastAppliedConfig)

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 All @@ -348,16 +403,18 @@ func kustomizationResourceUpdate(d *schema.ResourceData, m interface{}) error {
id := string(resp.GetUID())
d.SetId(id)

d.Set("manifest", getLastAppliedConfig(resp, gzipLastAppliedConfig))
lac := extractLastAppliedConfig(resp, extractor, gzipLastAppliedConfig)
d.Set("manifest", lac)

return kustomizationResourceRead(d, m)
}

func kustomizationResourceDelete(d *schema.ResourceData, m interface{}) error {
client := m.(*Config).Client
mapper := m.(*Config).Mapper
extractor := m.(*Config).Extractor

km := newKManifest(mapper, client)
km := newKManifest(mapper, client, extractor)

err := parseResourceData(km, d.Get("manifest").(string))
if err != nil {
Expand Down
Loading

0 comments on commit f680060

Please sign in to comment.