This is an automated email from the ASF dual-hosted git repository.
zhongxjian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git
The following commit(s) were added to refs/heads/master by this push:
new 2ef144dc [operator] Supplementary code comments v3 (#647)
2ef144dc is described below
commit 2ef144dcd9cd77c01275f3ce79360489c194f0f6
Author: Jian Zhong <[email protected]>
AuthorDate: Wed Mar 19 17:30:49 2025 +0800
[operator] Supplementary code comments v3 (#647)
---
operator/cmd/cluster/install.go | 2 +-
operator/cmd/cluster/uninstall.go | 2 +-
operator/pkg/helm/helm.go | 2 +-
operator/pkg/install/installer.go | 2 +-
operator/pkg/install/waiter.go | 4 +-
operator/pkg/schema/impl.go | 47 ++++----
operator/pkg/uninstall/uninstaller.go | 12 ++
operator/pkg/util/clog/clog.go | 17 ++-
operator/pkg/util/common.go | 24 ----
operator/pkg/util/dmultierr/dmultierr.go | 2 +
operator/pkg/util/errs.go | 16 +++
operator/pkg/util/k8s.go | 3 +
operator/pkg/util/label.go | 1 +
operator/pkg/util/pointer/pointer.go | 4 +
operator/pkg/util/progress/progress.go | 63 +++++++---
operator/pkg/values/map.go | 199 +++++++++++++++++--------------
16 files changed, 233 insertions(+), 167 deletions(-)
diff --git a/operator/cmd/cluster/install.go b/operator/cmd/cluster/install.go
index 91d7ec26..de41ab33 100644
--- a/operator/cmd/cluster/install.go
+++ b/operator/cmd/cluster/install.go
@@ -123,7 +123,7 @@ func Install(kubeClient kube.CLIClient, rootArgs *RootArgs,
iArgs *installArgs,
Kube: kubeClient,
Values: vals,
WaitTimeout: iArgs.waitTimeout,
- ProgressInfo: progress.NewInfo(),
+ ProgressInfo: progress.NewLog(),
Logger: cl,
}
if err := i.InstallManifests(manifests); err != nil {
diff --git a/operator/cmd/cluster/uninstall.go
b/operator/cmd/cluster/uninstall.go
index 91987a6f..570fc017 100644
--- a/operator/cmd/cluster/uninstall.go
+++ b/operator/cmd/cluster/uninstall.go
@@ -91,7 +91,7 @@ func Uninstall(cmd *cobra.Command, ctx cli.Context, rootArgs
*RootArgs, uiArgs *
return err
}
- pl := progress.NewInfo()
+ pl := progress.NewLog()
if uiArgs.remove && uiArgs.filenames != "" {
cl.LogAndPrint("Purge uninstall will remove all Dubbo
resources, ignoring the specified revision or operator file")
}
diff --git a/operator/pkg/helm/helm.go b/operator/pkg/helm/helm.go
index 49a9ce93..54437411 100644
--- a/operator/pkg/helm/helm.go
+++ b/operator/pkg/helm/helm.go
@@ -82,7 +82,7 @@ func renderChart(namespace string, chrtVals values.Map, chrt
*chart.Chart) ([]st
}
crdFiles := chrt.CRDObjects()
if chrt.Metadata.Name == BaseChartName {
- values.GetPathHelper[bool](chrtVals,
"base.enableDubboConfigCRD")
+ values.GetPathAs[bool](chrtVals, "base.enableDubboConfigCRD")
}
var warnings Warnings
keys := make([]string, 0, len(files))
diff --git a/operator/pkg/install/installer.go
b/operator/pkg/install/installer.go
index 4a4348a3..9b23797b 100644
--- a/operator/pkg/install/installer.go
+++ b/operator/pkg/install/installer.go
@@ -45,7 +45,7 @@ type Installer struct {
SkipWait bool
Kube kube.CLIClient
Values values.Map
- ProgressInfo *progress.Info
+ ProgressInfo *progress.Log
WaitTimeout time.Duration
Logger clog.Logger
}
diff --git a/operator/pkg/install/waiter.go b/operator/pkg/install/waiter.go
index c506111d..279be738 100644
--- a/operator/pkg/install/waiter.go
+++ b/operator/pkg/install/waiter.go
@@ -43,7 +43,7 @@ type deployment struct {
// WaitForResources polls to get the current status of various objects that
are not immediately ready
// until all are ready or a timeout is reached.
-func WaitForResources(objects []manifest.Manifest, client kube.CLIClient,
waitTimeout time.Duration, dryRun bool, l *progress.ManifestInfo) error {
+func WaitForResources(objects []manifest.Manifest, client kube.CLIClient,
waitTimeout time.Duration, dryRun bool, l *progress.ManifestLog) error {
if dryRun {
return nil
}
@@ -78,7 +78,7 @@ func WaitForResources(objects []manifest.Manifest, client
kube.CLIClient, waitTi
return nil
}
-func waitForResources(objects []manifest.Manifest, k kube.Client, l
*progress.ManifestInfo) (bool, []string, map[string]string, error) {
+func waitForResources(objects []manifest.Manifest, k kube.Client, l
*progress.ManifestLog) (bool, []string, map[string]string, error) {
pods := []corev1.Pod{}
deployments := []deployment{}
statefulsets := []*appsv1.StatefulSet{}
diff --git a/operator/pkg/schema/impl.go b/operator/pkg/schema/impl.go
index 68af51f3..e2e847c7 100644
--- a/operator/pkg/schema/impl.go
+++ b/operator/pkg/schema/impl.go
@@ -31,7 +31,7 @@ type schemaImpl struct {
gvk config.GroupVersionKind
plural string
clusterScoped bool
- goPkg string
+ goPackage string
proto string
versionAliases []string
apiVersion string
@@ -39,6 +39,16 @@ type schemaImpl struct {
statusType reflect.Type
}
+// Schema for a resource.
+type Schema interface {
+ fmt.Stringer
+ GroupVersionResource() schema.GroupVersionResource
+ GroupVersionKind() config.GroupVersionKind
+ GroupVersionAliasKinds() []config.GroupVersionKind
+ Validate() error
+ IsClusterScoped() bool
+}
+
func (s *schemaImpl) GroupVersionAliasKinds() []config.GroupVersionKind {
gvks := make([]config.GroupVersionKind, len(s.versionAliases))
for i, va := range s.versionAliases {
@@ -49,8 +59,12 @@ func (s *schemaImpl) GroupVersionAliasKinds()
[]config.GroupVersionKind {
return gvks
}
-func (s *schemaImpl) IsClusterScoped() bool {
- return s.clusterScoped
+func (s *schemaImpl) String() string {
+ return fmt.Sprintf("[Schema](%s, %q, %s)", s.Kind(), s.goPackage,
s.proto)
+}
+
+func (s *schemaImpl) APIVersion() string {
+ return s.apiVersion
}
func (s *schemaImpl) Kind() string {
@@ -69,16 +83,12 @@ func (s *schemaImpl) Plural() string {
return s.plural
}
-func (s *schemaImpl) GroupVersionKind() config.GroupVersionKind {
- return s.gvk
-}
-
-func (s *schemaImpl) InClusterScoped() bool {
+func (s *schemaImpl) IsClusterScoped() bool {
return s.clusterScoped
}
-func (s *schemaImpl) String() string {
- return fmt.Sprintf("[Schema](%s, %q, %s)", s.Kind(), s.goPkg, s.proto)
+func (s *schemaImpl) GroupVersionKind() config.GroupVersionKind {
+ return s.gvk
}
func (s *schemaImpl) GroupVersionResource() schema.GroupVersionResource {
@@ -96,6 +106,7 @@ func (s *schemaImpl) Validate() (err error) {
return
}
+// Builder for a Schema.
type Builder struct {
Identifier string
Plural string
@@ -111,6 +122,7 @@ type Builder struct {
Synthetic bool
}
+// BuildNoValidate builds the Schema without checking the fields.
func (b Builder) BuildNoValidate() Schema {
return &schemaImpl{
gvk: config.GroupVersionKind{
@@ -120,7 +132,7 @@ func (b Builder) BuildNoValidate() Schema {
},
plural: b.Plural,
clusterScoped: b.ClusterScoped,
- goPkg: b.ProtoPackage,
+ goPackage: b.ProtoPackage,
proto: b.Proto,
apiVersion: b.Group + "/" + b.Version,
reflectType: b.ReflectType,
@@ -128,14 +140,17 @@ func (b Builder) BuildNoValidate() Schema {
}
}
+// Build a Schema instance.
func (b Builder) Build() (Schema, error) {
s := b.BuildNoValidate()
+ // Validate the schema.
if err := s.Validate(); err != nil {
return nil, err
}
return s, nil
}
+// MustBuild calls Build and panics if it fails.
func (b Builder) MustBuild() Schema {
s, err := b.Build()
if err != nil {
@@ -144,17 +159,9 @@ func (b Builder) MustBuild() Schema {
return s
}
-type Schema interface {
- fmt.Stringer
- GroupVersionResource() schema.GroupVersionResource
- GroupVersionKind() config.GroupVersionKind
- GroupVersionAliasKinds() []config.GroupVersionKind
- Validate() error
- IsClusterScoped() bool
-}
-
var protoMessageType = protoregistry.GlobalTypes.FindMessageByName
+// getProtoMessageType returns the Go lang type of the proto with the
specified name.
func getProtoMessageType(protoMessageName string) reflect.Type {
t, err := protoMessageType(protoreflect.FullName(protoMessageName))
if err != nil || t == nil {
diff --git a/operator/pkg/uninstall/uninstaller.go
b/operator/pkg/uninstall/uninstaller.go
index 5d1809a3..92321a72 100644
--- a/operator/pkg/uninstall/uninstaller.go
+++ b/operator/pkg/uninstall/uninstaller.go
@@ -35,19 +35,29 @@ import (
)
var (
+ // ClusterResources are resource types the operator prunes, ordered by
which types should be deleted, first to last.
ClusterResources = []schema.GroupVersionKind{
{Group: "rbac.authorization.k8s.io", Version: "v1", Kind:
"ClusterRole"},
{Group: "rbac.authorization.k8s.io", Version: "v1", Kind:
"ClusterRoleBinding"},
+ // Cannot currently prune CRDs because this will also wipe out
user config.
+ // {Group: "apiextensions.k8s.io", Version: "v1beta1", Kind:
name.CRDStr},
}
+ // ClusterControlPlaneResources lists cluster scope resources types
which should be deleted during uninstall command.
ClusterControlPlaneResources = []schema.GroupVersionKind{
{Group: "rbac.authorization.k8s.io", Version: "v1", Kind:
"ClusterRole"},
{Group: "rbac.authorization.k8s.io", Version: "v1", Kind:
"ClusterRoleBinding"},
}
+ // AllClusterResources lists all cluster scope resources types which
should be deleted in purge case, including CRD.
AllClusterResources = append(ClusterResources,
gvk.CustomResourceDefinition.Kubernetes(),
)
)
+// GetRemovedResources get the list of resources to be removed
+// 1. if includeClusterResources is false, we list the namespaced resources by
matching revision and component labels.
+// 2. if includeClusterResources is true, we list the namespaced and cluster
resources by component labels only.
+// If componentName is not empty, only resources associated with specific
components would be returned
+// UnstructuredList of objects and corresponding list of name kind hash of
k8sObjects would be returned
func GetRemovedResources(kc kube.CLIClient, dopName, dopNamespace string,
includeClusterResources bool) ([]*unstructured.UnstructuredList, error) {
var usList []*unstructured.UnstructuredList
labels := make(map[string]string)
@@ -85,6 +95,7 @@ func GetRemovedResources(kc kube.CLIClient, dopName,
dopNamespace string, includ
return usList, nil
}
+// NamespacedResources gets specific pruning resources based on the k8s version
func NamespacedResources() []schema.GroupVersionKind {
res := []schema.GroupVersionKind{
gvk.Deployment.Kubernetes(),
@@ -103,6 +114,7 @@ func PrunedResourcesSchemas() []schema.GroupVersionKind {
return append(NamespacedResources(), ClusterResources...)
}
+// DeleteObjectsList removed resources that are in the slice of
UnstructuredList.
func DeleteObjectsList(c kube.CLIClient, dryRun bool, log clog.Logger,
objectsList []*unstructured.UnstructuredList) error {
var errs util.Errors
for _, ol := range objectsList {
diff --git a/operator/pkg/util/clog/clog.go b/operator/pkg/util/clog/clog.go
index cae20d09..826934df 100644
--- a/operator/pkg/util/clog/clog.go
+++ b/operator/pkg/util/clog/clog.go
@@ -24,12 +24,7 @@ import (
"os"
)
-type ConsoleLogger struct {
- stdOut io.Writer
- stdErr io.Writer
- scope *log.Scope
-}
-
+// Logger provides optional log taps for console and test buffer outputs.
type Logger interface {
LogAndPrint(v ...any)
LogAndError(v ...any)
@@ -40,6 +35,15 @@ type Logger interface {
Print(s string)
}
+// ConsoleLogger is the struct used for mesh command.
+type ConsoleLogger struct {
+ stdOut io.Writer
+ stdErr io.Writer
+ scope *log.Scope
+}
+
+// NewConsoleLogger creates a new logger and returns a pointer to it.
+// stdOut and stdErr can be used to capture output for testing.
func NewConsoleLogger(stdOut, stdErr io.Writer, scope *log.Scope)
*ConsoleLogger {
s := scope
if s == nil {
@@ -52,6 +56,7 @@ func NewConsoleLogger(stdOut, stdErr io.Writer, scope
*log.Scope) *ConsoleLogger
}
}
+// NewDefaultLogger creates a new logger that outputs to stdout/stderr at
default scope.
func NewDefaultLogger() *ConsoleLogger {
return NewConsoleLogger(os.Stdout, os.Stderr, nil)
}
diff --git a/operator/pkg/util/common.go b/operator/pkg/util/common.go
deleted file mode 100644
index 658df579..00000000
--- a/operator/pkg/util/common.go
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package util
-
-import "strings"
-
-func IsFilePath(path string) bool {
- return strings.Contains(path, "/") || strings.Contains(path, ".")
-}
diff --git a/operator/pkg/util/dmultierr/dmultierr.go
b/operator/pkg/util/dmultierr/dmultierr.go
index 58cf8b0d..2eecd4cd 100644
--- a/operator/pkg/util/dmultierr/dmultierr.go
+++ b/operator/pkg/util/dmultierr/dmultierr.go
@@ -23,6 +23,8 @@ import (
"strings"
)
+// MultiErrorFormat provides a format for multierrors. This matches the
default format, but if there
+// is only one error we will not expand to multiple lines.
func MultiErrorFormat() multierror.ErrorFormatFunc {
return func(errors []error) string {
if len(errors) == 1 {
diff --git a/operator/pkg/util/errs.go b/operator/pkg/util/errs.go
index 18e2e5c6..93dc4409 100644
--- a/operator/pkg/util/errs.go
+++ b/operator/pkg/util/errs.go
@@ -23,12 +23,20 @@ const (
defaultSeparator = ", "
)
+// Errors is a slice of error.
type Errors []error
+// String implements the stringer#String method.
+func (e Errors) String() string {
+ return e.Error()
+}
+
+// Error implements the error#Error method.
func (e Errors) Error() string {
return ToString(e, defaultSeparator)
}
+// ToError returns an error from Errors.
func (e Errors) ToError() error {
if len(e) == 0 {
return nil
@@ -36,6 +44,8 @@ func (e Errors) ToError() error {
return fmt.Errorf("%s", e)
}
+// ToString returns a string representation of errors, with elements separated
by separator string. Any nil errors in the
+// slice are skipped.
func ToString(errors []error, separator string) string {
var out string
for i, e := range errors {
@@ -50,6 +60,8 @@ func ToString(errors []error, separator string) string {
return out
}
+// NewErrs returns a slice of error with a single element err.
+// If err is nil, returns nil.
func NewErrs(err error) Errors {
if err == nil {
return nil
@@ -57,6 +69,8 @@ func NewErrs(err error) Errors {
return []error{err}
}
+// AppendErr appends err to errors if it is not nil and returns the result.
+// If err is nil, it is not appended.
func AppendErr(errors []error, err error) Errors {
if err == nil {
if len(errors) == 0 {
@@ -67,6 +81,8 @@ func AppendErr(errors []error, err error) Errors {
return append(errors, err)
}
+// AppendErrs appends newErrs to errors and returns the result.
+// If newErrs is empty, nothing is appended.
func AppendErrs(errors []error, newErrs []error) Errors {
if len(newErrs) == 0 {
return errors
diff --git a/operator/pkg/util/k8s.go b/operator/pkg/util/k8s.go
index 6c96c65b..9b63429e 100644
--- a/operator/pkg/util/k8s.go
+++ b/operator/pkg/util/k8s.go
@@ -26,13 +26,16 @@ import (
"k8s.io/client-go/kubernetes"
)
+// CreateNamespace creates a namespace using the given k8s interface.
func CreateNamespace(cs kubernetes.Interface, namespace string, dryRun bool)
error {
if dryRun {
return nil
}
if namespace == "" {
+ // Setup default namespace.
namespace = "dubbo-system"
}
+ // check if the namespace already exists. If yes, do nothing. If no,
create a new one.
if _, err := cs.CoreV1().Namespaces().Get(context.TODO(), namespace,
metav1.GetOptions{}); err != nil {
if errors.IsNotFound(err) {
ns := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{
diff --git a/operator/pkg/util/label.go b/operator/pkg/util/label.go
index 40465cb9..f92db623 100644
--- a/operator/pkg/util/label.go
+++ b/operator/pkg/util/label.go
@@ -22,6 +22,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
+// SetLabel is a helper function which sets the specified label and value on
the specified object.
func SetLabel(resource runtime.Object, label, value string) error {
resourceAccessor, err := meta.Accessor(resource)
if err != nil {
diff --git a/operator/pkg/util/pointer/pointer.go
b/operator/pkg/util/pointer/pointer.go
index 40f73e1c..a0291508 100644
--- a/operator/pkg/util/pointer/pointer.go
+++ b/operator/pkg/util/pointer/pointer.go
@@ -17,15 +17,19 @@
package pointer
+// Of returns a pointer to the input. In most cases, callers should just do
&t. However, in some cases
+// Go cannot take a pointer. For example, `ptr.Of(f())`.
func Of[T any](t T) *T {
return &t
}
+// Empty returns an empty T type
func Empty[T any]() T {
var empty T
return empty
}
+// NonEmptyOrDefault returns t if its non-empty, or else def.
func NonEmptyOrDefault[T comparable](t T, def T) T {
var empty T
if t != empty {
diff --git a/operator/pkg/util/progress/progress.go
b/operator/pkg/util/progress/progress.go
index d84c733e..48422465 100644
--- a/operator/pkg/util/progress/progress.go
+++ b/operator/pkg/util/progress/progress.go
@@ -29,6 +29,8 @@ import (
type InstallState int
+const inProgress = `{{ yellow (cycle . "-" "-" "-" " ") }} `
+
const (
StateInstalling InstallState = iota
StatePruning
@@ -36,24 +38,28 @@ const (
StateUninstallComplete
)
-const inProgress = `{{ yellow (cycle . "-" "-" "-" " ") }} `
-
-type Info struct {
- components map[string]*ManifestInfo
+// Log records the progress of an installation
+// This aims to provide information about the install of multiple components
in parallel, while working
+// around the limitations of the pb library, which will only support single
lines. To do this, we aggregate
+// the current components into a single line, and as components complete there
final state is persisted to a new line.
+type Log struct {
+ components map[string]*ManifestLog
state InstallState
bar *pb.ProgressBar
mu sync.Mutex
template string
}
-func NewInfo() *Info {
- return &Info{
- components: map[string]*ManifestInfo{},
+func NewLog() *Log {
+ return &Log{
+ components: map[string]*ManifestLog{},
bar: createBar(),
}
}
-func (i *Info) createStatus(maxWidth int) string {
+// createStatus will return a string to report the current status.
+// ex: - Processing resources for components. Waiting for foo, bar.
+func (i *Log) createStatus(maxWidth int) string {
comps := make([]string, 0, len(i.components))
wait := make([]string, 0, len(i.components))
for c, l := range i.components {
@@ -68,17 +74,20 @@ func (i *Info) createStatus(maxWidth int) string {
}
prefix := inProgress
if !i.bar.GetBool(pb.Terminal) {
+ // If we aren't a terminal, no need to spam extra lines
prefix = `{{ yellow "-" }} `
}
+ // reduce by 2 to allow for the "- " that will be added below
maxWidth -= 2
if maxWidth > 0 && len(msg) > maxWidth {
return prefix + msg[:maxWidth-3] + "..."
}
+ // cycle will alternate between "-" and " ". "-" is given multiple
times to avoid quick flashing back and forth
return prefix + msg
}
-func (i *Info) NewComponent(comp string) *ManifestInfo {
- mi := &ManifestInfo{
+func (i *Log) NewComponent(comp string) *ManifestLog {
+ mi := &ManifestLog{
report: i.reportProgress(comp),
}
i.mu.Lock()
@@ -86,13 +95,20 @@ func (i *Info) NewComponent(comp string) *ManifestInfo {
i.components[comp] = mi
return mi
}
-func (i *Info) reportProgress(componentName string) func() {
+
+// reportProgress will report an update for a given component
+// Because the bar library does not support multiple lines/bars at once, we
need to aggregate current
+// progress into a single line. For example "Waiting for x, y, z". Once a
component completes, we want
+// a new line created so the information is not lost. To do this, we spin up a
new bar with the remaining components
+// on a new line, and create a new bar. For example, this becomes "x
succeeded", "waiting for y, z".
+func (i *Log) reportProgress(componentName string) func() {
return func() {
compName := component.Name(componentName)
cliName := component.UserFacingCompName(compName)
i.mu.Lock()
defer i.mu.Unlock()
comp := i.components[componentName]
+ // The component has completed
comp.mu.Lock()
finished := comp.finished
compErr := comp.err
@@ -107,7 +123,9 @@ func (i *Info) reportProgress(componentName string) func() {
} else {
i.SetMessage(fmt.Sprintf(`{{ read "✘" }} %s
encountered an error: %s`, cliName, compErr), true)
}
+ // Close the bar out, outputting a new line
delete(i.components, componentName)
+ // Now we create a new bar, which will have the
remaining components
i.bar = createBar()
return
}
@@ -115,7 +133,9 @@ func (i *Info) reportProgress(componentName string) func() {
}
}
-func (i *Info) SetMessage(status string, finish bool) {
+func (i *Log) SetMessage(status string, finish bool) {
+ // if we are not a terminal and there is no change, do not write
+ // This avoids redundant lines
if !i.bar.GetBool(pb.Terminal) && status == i.template {
return
}
@@ -127,7 +147,7 @@ func (i *Info) SetMessage(status string, finish bool) {
i.bar.Write()
}
-func (i *Info) SetState(state InstallState) {
+func (i *Log) SetState(state InstallState) {
i.mu.Lock()
defer i.mu.Unlock()
i.state = state
@@ -143,22 +163,27 @@ func (i *Info) SetState(state InstallState) {
}
}
+// For testing only
var testWriter *io.Writer
func createBar() *pb.ProgressBar {
+ // Don't set a total and use Static so we can explicitly control when
you write. This is needed
+ // for handling the multiline issues.
bar := pb.New(0)
bar.Set(pb.Static, true)
if testWriter != nil {
bar.SetWriter(*testWriter)
}
bar.Start()
+ // if we aren't a terminal, we will return a new line for each new
message
if !bar.GetBool(pb.Terminal) {
bar.Set(pb.ReturnSymbol, "\n")
}
return bar
}
-type ManifestInfo struct {
+// ManifestLog records progress for a single component
+type ManifestLog struct {
report func()
err string
waiting []string
@@ -166,13 +191,13 @@ type ManifestInfo struct {
mu sync.Mutex
}
-func (mi *ManifestInfo) ReportProgress() {
+func (mi *ManifestLog) ReportProgress() {
if mi == nil {
return
}
}
-func (mi *ManifestInfo) ReportFinished() {
+func (mi *ManifestLog) ReportFinished() {
if mi == nil {
return
}
@@ -182,7 +207,7 @@ func (mi *ManifestInfo) ReportFinished() {
mi.report()
}
-func (mi *ManifestInfo) ReportError(err string) {
+func (mi *ManifestLog) ReportError(err string) {
if mi == nil {
return
}
@@ -192,7 +217,7 @@ func (mi *ManifestInfo) ReportError(err string) {
mi.report()
}
-func (mi *ManifestInfo) ReportWaiting(resources []string) {
+func (mi *ManifestLog) ReportWaiting(resources []string) {
if mi == nil {
return
}
@@ -202,7 +227,7 @@ func (mi *ManifestInfo) ReportWaiting(resources []string) {
mi.report()
}
-func (p *ManifestInfo) waitingResources() []string {
+func (p *ManifestLog) waitingResources() []string {
p.mu.Lock()
defer p.mu.Unlock()
return p.waiting
diff --git a/operator/pkg/values/map.go b/operator/pkg/values/map.go
index 8b87cc22..33de0b67 100644
--- a/operator/pkg/values/map.go
+++ b/operator/pkg/values/map.go
@@ -28,8 +28,10 @@ import (
"strings"
)
+// Map is a wrapper around an untyped map, used throughout the operator
codebase for generic access.
type Map map[string]any
+// JSON serializes a Map to a JSON string.
func (m Map) JSON() string {
bytes, err := json.Marshal(m)
if err != nil {
@@ -38,6 +40,7 @@ func (m Map) JSON() string {
return string(bytes)
}
+// YAML serializes a Map to a YAML string.
func (m Map) YAML() string {
bytes, err := yaml.Marshal(m)
if err != nil {
@@ -46,6 +49,7 @@ func (m Map) YAML() string {
return string(bytes)
}
+// MapFromJSON constructs a Map from JSON
func MapFromJSON(input []byte) (Map, error) {
m := make(Map)
err := json.Unmarshal(input, &m)
@@ -55,6 +59,7 @@ func MapFromJSON(input []byte) (Map, error) {
return m, nil
}
+// MapFromYAML constructs a Map from YAML
func MapFromYAML(input []byte) (Map, error) {
m := make(Map)
err := yaml.Unmarshal(input, &m)
@@ -64,17 +69,6 @@ func MapFromYAML(input []byte) (Map, error) {
return m, nil
}
-func fromJSON[T any](overlay []byte) (T, error) {
- v := new(T)
- err := json.Unmarshal(overlay, &v)
- if err != nil {
- return pointer.Empty[T](), err
- }
- return *v, nil
-}
-
-func parsePath(key string) []string { return strings.Split(key, ".") }
-
func tableLookup(m Map, simple string) (Map, bool) {
v, ok := m[simple]
if !ok {
@@ -83,12 +77,17 @@ func tableLookup(m Map, simple string) (Map, bool) {
if vv, ok := v.(map[string]interface{}); ok {
return vv, true
}
+ // This catches a case where a value is of type Values, but doesn't
(for some
+ // reason) match the map[string]interface{}. This has been observed in
the
+ // wild, and might be a result of a nil map of type Values.
if vv, ok := v.(Map); ok {
return vv, true
}
return nil, false
}
+func parsePath(key string) []string { return strings.Split(key, ".") }
+
func (m Map) GetPathMap(s string) (Map, bool) {
current := m
for _, n := range parsePath(s) {
@@ -128,10 +127,12 @@ func splitPath(path string) []string {
for _, str := range pv {
if str != "" {
str = strings.ReplaceAll(str, "\\.", ".")
+ // Is str of the form node[expr], convert to "node",
"[expr]"?
nBracket := strings.IndexRune(str, '[')
if nBracket > 0 {
r = append(r, str[:nBracket], str[nBracket:])
} else {
+ // str is "[expr]" or "node"
r = append(r, str)
}
}
@@ -139,15 +140,9 @@ func splitPath(path string) []string {
return r
}
-func (m Map) GetPathString(s string) string {
- return GetPathHelper[string](m, s)
-}
-
-func (m Map) GetPathStringOr(s string, def string) string {
- return pointer.NonEmptyOrDefault(m.GetPathString(s), def)
-}
-
-func GetPathHelper[T any](m Map, name string) T {
+// GetPathAs is a helper function to get a patch value and cast it to a
specified type.
+// If the path is not found, or the cast fails, the zero value is returned.
+func GetPathAs[T any](m Map, name string) T {
v, ok := m.GetPath(name)
if !ok {
return pointer.Empty[T]()
@@ -156,6 +151,17 @@ func GetPathHelper[T any](m Map, name string) T {
return t
}
+// GetPathString is a helper around TryGetPathAs[string] to allow usage as a
method (otherwise impossible with generics)
+func (m Map) GetPathString(s string) string {
+ return GetPathAs[string](m, s)
+}
+
+// GetPathStringOr is a helper around TryGetPathAs[string] to allow usage as a
method (otherwise impossible with generics),
+// with an allowance for a default value if it is not found/not set.
+func (m Map) GetPathStringOr(s string, def string) string {
+ return pointer.NonEmptyOrDefault(m.GetPathString(s), def)
+}
+
func (m Map) GetPath(name string) (any, bool) {
current := any(m)
paths := splitPath(name)
@@ -204,6 +210,7 @@ func (m Map) GetPath(name string) (any, bool) {
return current, true
}
+// MustCastAsMap casts a value to a Map; if the value is not a map, it will
panic..
func MustCastAsMap(current any) Map {
m, ok := CastAsMap(current)
if !ok {
@@ -215,6 +222,7 @@ func MustCastAsMap(current any) Map {
return m
}
+// CastAsMap casts a value to a Map, if possible.
func CastAsMap(current any) (Map, bool) {
if m, ok := current.(Map); ok {
return m, true
@@ -225,48 +233,31 @@ func CastAsMap(current any) (Map, bool) {
return nil, false
}
+// ConvertMap translates a Map to a T, via JSON
func ConvertMap[T any](m Map) (T, error) {
return fromJSON[T]([]byte(m.JSON()))
}
-func extractIndex(seg string) (int, bool) {
- if !strings.HasPrefix(seg, "[") || !strings.HasSuffix(seg, "]") {
- return 0, false
- }
- sanitized := seg[1 : len(seg)-1]
- v, err := strconv.Atoi(sanitized)
+func fromJSON[T any](overlay []byte) (T, error) {
+ v := new(T)
+ err := json.Unmarshal(overlay, &v)
if err != nil {
- return 0, false
- }
- return v, true
-}
-
-func extractKeyValue(seg string) (string, string, bool) {
- if !strings.HasPrefix(seg, "[") || !strings.HasSuffix(seg, "]") {
- return "", "", false
+ return pointer.Empty[T](), err
}
- sanitized := seg[1 : len(seg)-1]
- return strings.Cut(sanitized, ":")
+ return *v, nil
}
-func (m Map) MergeFrom(other Map) {
- for k, v := range other {
- if vm, ok := v.(Map); ok {
- v = map[string]any(vm)
- }
- if v, ok := v.(map[string]any); ok {
- if bv, ok := m[k]; ok {
-
- if bv, ok := bv.(map[string]any); ok {
- Map(bv).MergeFrom(v)
- continue
- }
- }
- }
- m[k] = v
+// getPV returns the path and value components for the given set flag string,
which must be in path=value format.
+func getPV(setFlag string) (path string, value string) {
+ pv := strings.Split(setFlag, "=")
+ if len(pv) != 2 {
+ return setFlag, ""
}
+ path, value = strings.TrimSpace(pv[0]), strings.TrimSpace(pv[1])
+ return
}
+// SetPath applies values from a path like `key.subkey`, `key.[0].var`, or
`key.[name:foo]`.
func (m Map) SetPath(paths string, value any) error {
path := splitPath(paths)
base := m
@@ -276,6 +267,7 @@ func (m Map) SetPath(paths string, value any) error {
return nil
}
+// SetPaths applies values from input like `key.subkey=val`
func (m Map) SetPaths(paths ...string) error {
for _, sf := range paths {
p, v := getPV(sf)
@@ -291,40 +283,7 @@ func (m Map) SetPaths(paths ...string) error {
return nil
}
-func getPV(setFlag string) (path string, value string) {
- pv := strings.Split(setFlag, "=")
- if len(pv) != 2 {
- return setFlag, ""
- }
- path, value = strings.TrimSpace(pv[0]), strings.TrimSpace(pv[1])
- return
-}
-
-func parseValue(valueStr string) any {
- var value any
- if v, err := strconv.Atoi(valueStr); err == nil {
- value = v
- } else if v, err := strconv.ParseFloat(valueStr, 64); err == nil {
- value = v
- } else if v, err := strconv.ParseBool(valueStr); err == nil {
- value = v
- } else {
- value = strings.ReplaceAll(valueStr, "\\,", ",")
- }
- return value
-}
-
-func isAlwaysString(s string) bool {
- for _, a := range alwaysString {
- if strings.HasPrefix(s, a) {
- return true
- }
- }
- return false
-}
-
-var alwaysString = []string{}
-
+// SetSpecPaths applies values from input like `key.subkey=val`, and applies
them under 'spec'
func (m Map) SetSpecPaths(paths ...string) error {
for _, path := range paths {
if err := m.SetPaths("spec." + path); err != nil {
@@ -402,13 +361,69 @@ func extractKV(seg string) (string, string, bool) {
return strings.Cut(sanitized, ":")
}
-func GetValueForSetFlag(setFlags []string, path string) string {
- r := ""
- for _, sf := range setFlags {
- p, v := getPV(sf)
- if p == path {
- r = v
+func extractIndex(seg string) (int, bool) {
+ if !strings.HasPrefix(seg, "[") || !strings.HasSuffix(seg, "]") {
+ return 0, false
+ }
+ sanitized := seg[1 : len(seg)-1]
+ v, err := strconv.Atoi(sanitized)
+ if err != nil {
+ return 0, false
+ }
+ return v, true
+}
+
+func extractKeyValue(seg string) (string, string, bool) {
+ if !strings.HasPrefix(seg, "[") || !strings.HasSuffix(seg, "]") {
+ return "", "", false
+ }
+ sanitized := seg[1 : len(seg)-1]
+ return strings.Cut(sanitized, ":")
+}
+
+// alwaysString represents types that should always be decoded as strings
+var alwaysString = []string{}
+
+func isAlwaysString(s string) bool {
+ for _, a := range alwaysString {
+ if strings.HasPrefix(s, a) {
+ return true
}
}
- return r
+ return false
+}
+
+// parseValue parses string into a value.
+func parseValue(valueStr string) any {
+ var value any
+ if v, err := strconv.Atoi(valueStr); err == nil {
+ value = v
+ } else if v, err := strconv.ParseFloat(valueStr, 64); err == nil {
+ value = v
+ } else if v, err := strconv.ParseBool(valueStr); err == nil {
+ value = v
+ } else {
+ value = strings.ReplaceAll(valueStr, "\\,", ",")
+ }
+ return value
+}
+
+// MergeFrom does a key-wise merge between the current map and the passed in
map.
+// The other map has precedence, and the result will modify the current map.
+func (m Map) MergeFrom(other Map) {
+ for k, v := range other {
+ if vm, ok := v.(Map); ok {
+ v = map[string]any(vm)
+ }
+ if v, ok := v.(map[string]any); ok {
+ if bv, ok := m[k]; ok {
+
+ if bv, ok := bv.(map[string]any); ok {
+ Map(bv).MergeFrom(v)
+ continue
+ }
+ }
+ }
+ m[k] = v
+ }
}