talos/pkg/argsbuilder/argsbuilder_args.go
Mateusz Urbanek d1954278a1
feat: add extraArgs from service-account-issuer
In API Server, passing extra args with `service-account-issuer` will add them to default value.

Fixes #11694

Signed-off-by: Mateusz Urbanek <mateusz.urbanek@siderolabs.com>
2026-01-16 11:21:00 +01:00

168 lines
3.6 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package argsbuilder
import (
"fmt"
"slices"
"strings"
"github.com/siderolabs/gen/maps"
)
// Key represents an arg key.
type Key = string
// Value represents an arg value.
type Value = []string
// Args represents a set of args.
type Args map[Key]Value
// MustMerge implements the ArgsBuilder interface.
func (a Args) MustMerge(args Args, setters ...MergeOption) {
if err := a.Merge(args, setters...); err != nil {
panic(err)
}
}
// Merge implements the ArgsBuilder interface.
//
//nolint:gocyclo
func (a Args) Merge(args Args, setters ...MergeOption) error {
var opts MergeOptions
for _, s := range setters {
s(&opts)
}
policies := opts.Policies
if policies == nil {
policies = MergePolicies{}
}
for key, val := range args {
policy := policies[key]
switch policy {
case MergeDenied:
return NewDenylistError(key)
case MergeOverwrite:
a[key] = slices.Clone(val)
case MergeAppend:
existing := make([]string, 0, len(val)+len(a[key]))
existing = append(existing, a[key]...)
existing = append(existing, val...)
a[key] = existing
case MergePrepend:
existing := make([]string, 0, len(val)+len(a[key]))
existing = append(existing, val...)
existing = append(existing, a[key]...)
a[key] = existing
case MergeAdditive:
// 1. Join the existing []string slice into one string so we can Split it.
// This handles cases where a[key] might be ["a", "b"] or ["a,b"].
rawExisting := strings.Join(a[key], ",")
values := strings.Split(rawExisting, ",")
definedValues := map[string]struct{}{}
i := 0
for _, v := range values {
cleanV := strings.TrimSpace(v)
if cleanV != "" {
// Only keep if unique
if _, seen := definedValues[cleanV]; !seen {
definedValues[cleanV] = struct{}{}
values[i] = cleanV
i++
}
}
}
values = values[:i]
// 2. Join the incoming 'val' slice so we can SplitSeq over it.
rawIncoming := strings.Join(val, ",")
for v := range strings.SplitSeq(rawIncoming, ",") {
v = strings.TrimSpace(v)
if v != "" {
if _, defined := definedValues[v]; !defined {
values = append(values, v)
// Mark as defined to prevent duplicates within the incoming values too
definedValues[v] = struct{}{}
}
}
}
// 3. Join the results and wrap in a []string to satisfy the type.
a[key] = []string{strings.Join(values, ",")}
}
}
return nil
}
// Set implements the ArgsBuilder interface.
func (a Args) Set(k Key, v Value) ArgsBuilder {
a[k] = v
return a
}
// Args implements the ArgsBuilder interface.
func (a Args) Args() []string {
keys := maps.Keys(a)
slices.Sort(keys)
args := make([]string, 0, len(a))
for _, key := range keys {
vals := a[key]
for _, val := range vals {
args = append(
args,
fmt.Sprintf("--%s=%s", key, val),
)
}
}
return args
}
// Get returns an args value.
func (a Args) Get(k Key) Value {
return a[k]
}
// Contains checks if an arg key exists.
func (a Args) Contains(k Key) bool {
_, ok := a[k]
return ok
}
// DenyListError represents an error indicating that an argument was supplied
// that is not allowed.
type DenyListError struct {
s string
}
// NewDenylistError returns a DenyListError.
func NewDenylistError(s string) error {
return &DenyListError{s}
}
// Error implements the Error interface.
func (b *DenyListError) Error() string {
return fmt.Sprintf("extra arg %q is not allowed", b.s)
}