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

Fix #10012 - empty string as a parameter in etcd extra args #10018

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

jordanorc
Copy link

Proposed Changes

This modification checks if a parameter passed in --etcd-arg is an empty string, preventing it from being wrongly converted to an empty array by the function ToConfigFile.

Types of Changes

BugFix

Verification

  1. Build k3s following the tutorial in https://github.com/k3s-io/k3s/blob/master/BUILDING.md
  2. Run ./dist/artifacts/k3s server --cluster-init --etcd-arg=listen-metrics-urls=

Instead of throwing the error: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into Go struct field configYAML.listen-metrics-urls of type string, k3s will continue the startup process.

Testing

See linked issue

Linked Issues

#10012

User-Facing Change

NONE

@jordanorc jordanorc force-pushed the fix-empty-string-parameter-etcd-extra-args branch from 136e14c to a0a4885 Compare April 24, 2024 23:29
@@ -103,7 +103,7 @@ func (e ETCDConfig) ToConfigFile(extraArgs []string) (string, error) {
} else if time, err := time.ParseDuration(extraArg[1]); err == nil && (strings.Contains(lowerKey, "time") || strings.Contains(lowerKey, "duration") || strings.Contains(lowerKey, "interval") || strings.Contains(lowerKey, "retention")) {
// auto-compaction-retention is either a time.Duration or int, depending on version. If it is an int, it will be caught above.
s[key] = time
} else if err := yaml.Unmarshal([]byte(extraArg[1]), &stringArr); err == nil {
} else if err := yaml.Unmarshal([]byte(extraArg[1]), &stringArr); err == nil && (len(extraArg[1]) > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this belongs up on line 97? if len(extraArg) == 2 && len(extraArg[1]) > 0

Copy link
Author

Choose a reason for hiding this comment

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

If I do that, the parameter will not be inserted into the s map (https://github.com/jordanorc/k3s/blob/a0a4885eafd39163e417601029bd19073ced7cdd/pkg/daemons/executor/executor.go#L107) and will never be overridden with an empty string.

Copy link
Contributor

@brandond brandond Apr 26, 2024

Choose a reason for hiding this comment

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

OK but what is your intent here - if the arg value is an empty string, do you want the config value set to an empty string, or do you want to ensure that the key is removed so that it takes the default value?

What you're doing here will set the value to an empty string, which may be valid for config entries that expect a string value. That is not all of them, as shown by the special handling for integer and time/duration fields.

If your intent is to get the default value by removing the key from the config, then I believe a different approach is required.

@brandond
Copy link
Contributor

etcd args are already somewhat unique in that they are converted to config json, which has a side effect of deduplicating flags. Most other flags are simply appended to our defaults and passed into the component CLI for the component's internal flag parser to figure out.

With this change you will be able to unset/mask k3s default flags by setting them to an empty string, which will make the etcd args even more unique.

@jordanorc
Copy link
Author

jordanorc commented Apr 25, 2024

I think that might be the source of some confusion - the default isn't an empty value, and for the other --COMPONENT-arg flags, passing through an arg with an empty value won't restore the default. What you're asking for is the ability to unset or mask a default value that is set internally by k3s. That isn't currently possible.

@brandond I partially agree with you. Actually, the behavior between the --COMPONENT-arg is divergent. See an example of the --kube-apiserver-arg behavior. If I change some arg to empty, it is forwarded to kube-apiserver as an empty string (not an empty map).

Running the command:

k3s server --cluster-init --kube-apiserver-arg=authorization-mode=

Results in the log:

INFO[0000] Running kube-apiserver --advertise-port=6443 --allow-privileged=true --anonymous-auth=false --api-audiences=https://kubernetes.default.svc.cluster.local,k3s --authorization-mode= --bind-address=127.0.0.1 ...

The current behavior of --etcd-arg is to translate "" or null into an empty map ([]) and forward this value to etcd. Many of etcd values can be an empty string, and I cannot set it with --etcd-arg because of this bug.

Despite the fact that it is not possible to unset a value with --etcd-arg, it should be possible to set it to an empty string once many etcd args can be set to an empty string.

@@ -103,7 +103,7 @@ func (e ETCDConfig) ToConfigFile(extraArgs []string) (string, error) {
} else if time, err := time.ParseDuration(extraArg[1]); err == nil && (strings.Contains(lowerKey, "time") || strings.Contains(lowerKey, "duration") || strings.Contains(lowerKey, "interval") || strings.Contains(lowerKey, "retention")) {
// auto-compaction-retention is either a time.Duration or int, depending on version. If it is an int, it will be caught above.
s[key] = time
} else if err := yaml.Unmarshal([]byte(extraArg[1]), &stringArr); err == nil {
} else if err := yaml.Unmarshal([]byte(extraArg[1]), &stringArr); err == nil && (len(extraArg[1]) > 0) {
Copy link
Contributor

@brandond brandond Apr 26, 2024

Choose a reason for hiding this comment

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

OK but what is your intent here - if the arg value is an empty string, do you want the config value set to an empty string, or do you want to ensure that the key is removed so that it takes the default value?

What you're doing here will set the value to an empty string, which may be valid for config entries that expect a string value. That is not all of them, as shown by the special handling for integer and time/duration fields.

If your intent is to get the default value by removing the key from the config, then I believe a different approach is required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants