Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to set fields to null literral in go #2162

Open
tgermain opened this issue May 31, 2024 · 3 comments
Open

How to set fields to null literral in go #2162

tgermain opened this issue May 31, 2024 · 3 comments
Labels
bug Something isn't working @component/cdk8s-core Issue related to cdk8s-core jsii Stems from a jsii issue and requires its fix

Comments

@tgermain
Copy link

Description of the bug:

I'm using cdk8s with go and I'm having trouble setting some fields to null literal.

Why do I need to set fields to null in the first place?

When creating/updating resource using kubectl apply, it does a 3 way merge between the existing k8s resource on the cluster and what you want to apply.

For instance, if you already have a k8s resource with a foo: bar label like:

apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    foo: bar
    baz: baz
  name: my-cm

and you want to remove the foo label, applying a resource like:

apiVersion: v1
kind: ConfigMap
metadata:
  labels:
  # no more foo label
    baz: baz
  name: my-cm

is not enough because of the 3 way merge. It will work if you try to apply the same manifest to a brand new k8s cluster but not on one where the my-cm configmap already have the foo label.

A common trick I've used in the past and that was implemented in kubernetes/kubernetes#40630 is to set the field you want to remove to null literal:

apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    foo: null
    baz: baz
  name: my-cm

This yaml manifest will remove the foo label if it's already present in the existing resource.

Reproduction Steps:

This is my test cdk8s app:

package main

import (
	"fmt"

	"example.com/cdk8s-demo/imports/k8s"
	"github.com/aws/jsii-runtime-go"
	"github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2"
)

func main() {
	app := cdk8s.NewApp(&cdk8s.AppProps{})

	chart := cdk8s.NewChart(app, jsii.String("my-chart"), &cdk8s.ChartProps{})

	labels := map[string]*string{
		"foo": jsii.String("bar"),
		"set-to-null": jsii.String("to-be-replaced"),
	}
	k8s.NewKubeConfigMap(chart, jsii.String("my-cm"), &k8s.KubeConfigMapProps{
		Metadata: &k8s.ObjectMeta{
			Labels: &labels,
		}})
	fmt.Println(*app.SynthYaml())
}

It a go module named example.com/cdk8s-demo/imports/k8s.
Under example.com/cdk8s-demo/imports/k8s I've imported k8s api objects using cdk8s import k8s -l go.

When run using go run main.go it produce:

apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    foo: bar
    set-to-null: to-be-replaced
  name: my-chart-my-cm-c841940f

Environment:

  • Framework Version: github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2 v2.68.71
  • OS: linux

Other:

What I have already tried:

Using "set-to-null": jsii.String(""), -> it renders an empty string in the yaml (expected).

Using "set-to-null": jsii.String("null"), -> it renders a "null" string in the yaml (expected).

Using json-patch:

package main

import (
	"fmt"

	"example.com/cdk8s-demo/imports/k8s"
	"github.com/aws/jsii-runtime-go"
	"github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2"
)

func main() {
	app := cdk8s.NewApp(&cdk8s.AppProps{})

	chart := cdk8s.NewChart(app, jsii.String("my-chart"), &cdk8s.ChartProps{})

	labels := map[string]*string{
		"foo": jsii.String("bar"),
		"set-to-null": jsii.String(""),
	}
	cm:=k8s.NewKubeConfigMap(chart, jsii.String("my-cm"), &k8s.KubeConfigMapProps{
		Metadata: &k8s.ObjectMeta{
			Labels: &labels,
		}})

	cm.AddJsonPatch(cdk8s.JsonPatch_Replace(jsii.String("/metadata/labels/set-to-null"),"null"))
	fmt.Println(*app.SynthYaml())
}

cm.AddJsonPatch(cdk8s.JsonPatch_Replace(jsii.String("/metadata/labels/set-to-null"),"null")) -> render a "null" string in yaml
cm.AddJsonPatch(cdk8s.JsonPatch_Replace(jsii.String("/metadata/labels/set-to-null"),"")) -> render a "" string in yaml

Using pointer to empty interface:

	var i interface{}
	cm.AddJsonPatch(cdk8s.JsonPatch_Replace(jsii.String("/metadata/labels/set-to-null"), &i))

-> it removes the field completely.

I think a json path like {"op": "replace","path": "/metadata/labels/set-to-null","value": null} should work but I think the null literal in the json patch is lost at some point when go ---call---> jsii ---call---> typescript.


This is 🐛 Bug Report

@tgermain tgermain added bug Something isn't working needs-triage Priority and effort undetermined yet labels May 31, 2024
@iliapolo
Copy link
Member

iliapolo commented Jun 1, 2024

Nice one. I think JsonPatch is an ok API for this, assuming it works of course :)

The thing is, it does work in typescript:

cdk8s.ApiObject.of(cm).addJsonPatch(cdk8s.JsonPatch.replace('/metadata/labels/set-to-null', null));

In golang, the offending code is the auto generated validations jsii produces:

func validateJsonPatch_AddParameters(path *string, value interface{}) error {
	if path == nil {
		return fmt.Errorf("parameter path is required, but nil was provided")
	}

	if value == nil {
		return fmt.Errorf("parameter value is required, but nil was provided")
	}

	return nil
}

This sounds like a generic jsii issue, i'll check and update here.

Thanks for reporting this!

@tgermain
Copy link
Author

tgermain commented Jun 3, 2024

I've tried to make my own version of JsonPatch_Replace in order to bypass the validation that prevent me from passing nil as the value of the patch:

package main

import (
	"fmt"

	"example.com/cdk8s-demo/imports/k8s"
	"github.com/aws/jsii-runtime-go"
	"github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2"

	_jsii_ "github.com/aws/jsii-runtime-go/runtime"
	_init_ "github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2/jsii"
)

func main() {
	app := cdk8s.NewApp(&cdk8s.AppProps{})

	chart := cdk8s.NewChart(app, jsii.String("my-chart"), &cdk8s.ChartProps{})

	labels := map[string]*string{
		"foo":         jsii.String("bar"),
		"set-to-null": jsii.String(""),
	}
	cm := k8s.NewKubeConfigMap(chart, jsii.String("my-cm"), &k8s.KubeConfigMapProps{
		Metadata: &k8s.ObjectMeta{
			Labels: &labels,
		}})

	var i interface{}
	// cm.AddJsonPatch(cdk8s.JsonPatch_Replace(jsii.String("/metadata/labels/set-to-null"), &i))
	cm.AddJsonPatch(no_validation_JsonPatch_Replace(jsii.String("/metadata/labels/set-to-null"), &i))
	fmt.Println(*app.SynthYaml())
}


func no_validation_JsonPatch_Replace(path *string, value interface{}) cdk8s.JsonPatch {
	_init_.Initialize()

	var returns cdk8s.JsonPatch

	_jsii_.StaticInvoke(
		"cdk8s.JsonPatch",
		"replace",
		[]interface{}{path, value},
		&returns,
	)

	return returns
}

It doesn't break while going through jsii but it produces:

apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    foo: bar
  name: my-chart-my-cm-c841940f

@iliapolo
Copy link
Member

iliapolo commented Jun 3, 2024

@tgermain I've opened a jsii issue. not much we can do on cdk8s side.

@iliapolo iliapolo added jsii Stems from a jsii issue and requires its fix and removed needs-triage Priority and effort undetermined yet labels Jun 3, 2024
@iliapolo iliapolo added the @component/cdk8s-core Issue related to cdk8s-core label Sep 20, 2024
@iliapolo iliapolo transferred this issue from cdk8s-team/cdk8s-core Sep 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working @component/cdk8s-core Issue related to cdk8s-core jsii Stems from a jsii issue and requires its fix
Projects
None yet
Development

No branches or pull requests

2 participants