diff --git a/cmd/config/internal/commands/cmdcreatesetter.go b/cmd/config/internal/commands/cmdcreatesetter.go index 4dc9c5641..78ebb2d27 100644 --- a/cmd/config/internal/commands/cmdcreatesetter.go +++ b/cmd/config/internal/commands/cmdcreatesetter.go @@ -34,6 +34,8 @@ func NewCreateSetterRunner(parent string) *CreateSetterRunner { "kind of the Resource on which to create the setter.") set.Flags().StringVar(&r.Set.SetPartialField.Type, "type", "", "valid OpenAPI field type -- e.g. integer,boolean,string.") + set.Flags().BoolVar(&r.Set.SetPartialField.Partial, "partial", false, + "create a partial setter for only part of the field value.") fixDocs(parent, set) set.MarkFlagRequired("type") set.MarkFlagRequired("field") diff --git a/cmd/config/internal/commands/cmdset.go b/cmd/config/internal/commands/cmdset.go index d2ef769fc..914db5d54 100644 --- a/cmd/config/internal/commands/cmdset.go +++ b/cmd/config/internal/commands/cmdset.go @@ -27,6 +27,10 @@ func NewSetRunner(parent string) *SetRunner { } fixDocs(parent, c) r.Command = c + c.Flags().StringVar(&r.Perform.SetBy, "set-by", "", + "annotate the field with who set it") + c.Flags().StringVar(&r.Perform.Description, "description", "", + "annotate the field with a description of its value") return r } diff --git a/cmd/resource/Makefile b/cmd/resource/Makefile index f8567e8d7..29d262a6a 100644 --- a/cmd/resource/Makefile +++ b/cmd/resource/Makefile @@ -17,8 +17,8 @@ fmt: go fmt ./... generate: - #(which $(GOBIN)/mdtogo || go get sigs.k8s.io/kustomize/cmd/resource) - #GOBIN=$(GOBIN) go generate ./... + (which $(GOBIN)/mdtogo || go get sigs.k8s.io/kustomize/cmd/mdtogo) + GOBIN=$(GOBIN) go generate ./... license: (which $(GOBIN)/addlicense || go get github.com/google/addlicense) diff --git a/cmd/resource/go.mod b/cmd/resource/go.mod index 2ba9b0391..696f1b1f6 100644 --- a/cmd/resource/go.mod +++ b/cmd/resource/go.mod @@ -9,6 +9,7 @@ require ( k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655 k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90 sigs.k8s.io/controller-runtime v0.4.0 + sigs.k8s.io/kustomize/cmd/mdtogo v0.0.0-20191222005333-3900166fdf4f // indirect sigs.k8s.io/kustomize/kstatus v0.0.0-20191204200457-7c1b477ff62d sigs.k8s.io/kustomize/kyaml v0.0.0-20191202204815-0a19a5dbd9b8 ) diff --git a/cmd/resource/go.sum b/cmd/resource/go.sum index 40b7ef114..e3033136c 100644 --- a/cmd/resource/go.sum +++ b/cmd/resource/go.sum @@ -407,6 +407,9 @@ modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg= sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns= +sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= +sigs.k8s.io/kustomize/cmd/mdtogo v0.0.0-20191222005333-3900166fdf4f h1:Wdh26pJ0THtsuSB1DCkaLc1Ssv2NDddB7E7vCSdTHdg= +sigs.k8s.io/kustomize/cmd/mdtogo v0.0.0-20191222005333-3900166fdf4f/go.mod h1:arffnBwv6VTLUY3hxATxJ2fwNMWy92GSXm6UXEjFddQ= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH+UQM= diff --git a/cmd/resource/main.go b/cmd/resource/main.go index 433dfbdfa..588ffd3ec 100644 --- a/cmd/resource/main.go +++ b/cmd/resource/main.go @@ -1,3 +1,6 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + package main import ( diff --git a/cmd/resource/status/cmd/events.go b/cmd/resource/status/cmd/events.go index 67e98aa8d..9102f707a 100644 --- a/cmd/resource/status/cmd/events.go +++ b/cmd/resource/status/cmd/events.go @@ -1,3 +1,6 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + package cmd import ( @@ -6,23 +9,27 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/cmd/resource/status/generateddocs/commands" "sigs.k8s.io/kustomize/kstatus/wait" "sigs.k8s.io/kustomize/kyaml/kio" ) +// GetEventsRunner returns a command EventsRunner. func GetEventsRunner() *EventsRunner { r := &EventsRunner{} c := &cobra.Command{ - Use: "events", - Short: "Events", - RunE: r.runE, + Use: "events DIR...", + Short: commands.EventsShort, + Long: commands.EventsLong, + Example: commands.EventsExamples, + RunE: r.runE, } c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true, "also print resources from subpackages.") c.Flags().DurationVar(&r.Interval, "interval", 2*time.Second, - "check every n seconds. Default is every 2 seconds.") + "check every n seconds.") c.Flags().DurationVar(&r.Timeout, "timeout", 60*time.Second, - "give up after n seconds. Default is 60 seconds.") + "give up after n seconds.") r.Command = c return r @@ -32,6 +39,8 @@ func EventsCommand() *cobra.Command { return GetEventsRunner().Command } +// EventsRunner captures the parameters for the command +// and contains the run function. type EventsRunner struct { IncludeSubpackages bool Interval time.Duration @@ -41,13 +50,17 @@ type EventsRunner struct { func (r *EventsRunner) runE(c *cobra.Command, args []string) error { ctx := context.Background() + + // Create a client and use it to set up a new resolver. client, err := getClient() if err != nil { return errors.Wrap(err, "error creating client") } - resolver := wait.NewResolver(client, r.Interval) + // Set up a CaptureIdentifierFilter and run all inputs through the + // filter with the pipeline to capture the inventory of resources + // which we are interested in. captureFilter := &CaptureIdentifiersFilter{} filters := []kio.Filter{captureFilter} @@ -70,12 +83,17 @@ func (r *EventsRunner) runE(c *cobra.Command, args []string) error { return errors.Wrap(err, "error reading manifests") } + // Create a new printer that knows how to print updates about + // resourdes and their aggregate status in the events format. printer := newEventPrinter(c.OutOrStdout(), c.OutOrStderr()) ctx, cancel := context.WithTimeout(ctx, r.Timeout) defer cancel() resChannel := resolver.WaitForStatus(ctx, captureFilter.Identifiers) + // Print events until the channel is closed. This will happen + // either because all resources has reached the Current status + // or it has timed out. for msg := range resChannel { printer.printEvent(msg) } diff --git a/cmd/resource/status/cmd/fetch.go b/cmd/resource/status/cmd/fetch.go index 8957b0b21..d49bd0f68 100644 --- a/cmd/resource/status/cmd/fetch.go +++ b/cmd/resource/status/cmd/fetch.go @@ -1,3 +1,6 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + package cmd import ( @@ -6,17 +9,21 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/cmd/resource/status/generateddocs/commands" "sigs.k8s.io/kustomize/kstatus/status" "sigs.k8s.io/kustomize/kstatus/wait" "sigs.k8s.io/kustomize/kyaml/kio" ) +// GetFetchRunner returns a command FetchRunner. func GetFetchRunner() *FetchRunner { r := &FetchRunner{} c := &cobra.Command{ - Use: "fetch", - Short: "Fetch", - RunE: r.runE, + Use: "fetch DIR...", + Short: commands.FetchShort, + Long: commands.FetchLong, + Example: commands.FetchExamples, + RunE: r.runE, } c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true, "also print resources from subpackages.") @@ -29,6 +36,8 @@ func FetchCommand() *cobra.Command { return GetFetchRunner().Command } +// FetchRunner captures the parameters for the command and contains +// the run function. type FetchRunner struct { IncludeSubpackages bool Command *cobra.Command @@ -36,6 +45,8 @@ type FetchRunner struct { func (r *FetchRunner) runE(c *cobra.Command, args []string) error { ctx := context.Background() + + // Create a new client and use it to set up a resolver. client, err := getClient() if err != nil { return errors.Wrap(err, "error creating client") @@ -43,6 +54,9 @@ func (r *FetchRunner) runE(c *cobra.Command, args []string) error { resolver := wait.NewResolver(client, time.Minute) + // Set up a CaptureIdentifierFilter and run all inputs through the + // filter with the pipeline to capture the inventory of resources + // which we are interested in. captureFilter := &CaptureIdentifiersFilter{} filters := []kio.Filter{captureFilter} @@ -65,16 +79,27 @@ func (r *FetchRunner) runE(c *cobra.Command, args []string) error { return errors.Wrap(err, "error reading manifests") } + // Pass in the inventory of resources to the FetchAndResolve function + // on the resolver. It will return the status (or an error) for each + // resource in the inventory. results := resolver.FetchAndResolve(ctx, captureFilter.Identifiers) + // Create new printer that knows how to print resource statuses + // in a table format and ask it to print the results. newTablePrinter(FetchStatusInfo{results}, c.OutOrStdout(), c.OutOrStderr(), false).Print() return nil } +// FetchStatusInfo wraps the results from the FetchAndResolve function +// to the format expected in the TablePrinter. type FetchStatusInfo struct { Results []wait.ResourceResult } +// CurrentStatus returns the latest information known about the +// status of each of the resources. For FetchStatusInfo, the result +// is never updated, so it just returns the information provided +// by the slice of wait.ResourceResult at creation. func (f FetchStatusInfo) CurrentStatus() StatusData { var resourceData []ResourceStatusData for _, res := range f.Results { diff --git a/cmd/resource/status/cmd/print.go b/cmd/resource/status/cmd/print.go index 6cf2f74fa..c729c589b 100644 --- a/cmd/resource/status/cmd/print.go +++ b/cmd/resource/status/cmd/print.go @@ -1,3 +1,6 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + package cmd import ( diff --git a/cmd/resource/status/cmd/util.go b/cmd/resource/status/cmd/util.go index c98374dfe..962a8de2e 100644 --- a/cmd/resource/status/cmd/util.go +++ b/cmd/resource/status/cmd/util.go @@ -1,3 +1,6 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + package cmd import ( @@ -19,6 +22,8 @@ func init() { _ = clientgoscheme.AddToScheme(scheme) } +// getClient returns a client for talking to a Kubernetes cluster. The client +// is from controller-runtime. func getClient() (client.Client, error) { config := ctrl.GetConfigOrDie() mapper, err := apiutil.NewDiscoveryRESTMapper(config) @@ -28,6 +33,8 @@ func getClient() (client.Client, error) { return client.New(config, client.Options{Scheme: scheme, Mapper: mapper}) } +// CaptureIdentifiersFilter implements the Filter interface in the kio package. It +// captures the identifiers for all resources passed through the pipeline. type CaptureIdentifiersFilter struct { Identifiers []wait.ResourceIdentifier } diff --git a/cmd/resource/status/cmd/wait.go b/cmd/resource/status/cmd/wait.go index 2d3edc31e..f19840f45 100644 --- a/cmd/resource/status/cmd/wait.go +++ b/cmd/resource/status/cmd/wait.go @@ -1,3 +1,6 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + package cmd import ( @@ -7,18 +10,21 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - + "sigs.k8s.io/kustomize/cmd/resource/status/generateddocs/commands" "sigs.k8s.io/kustomize/kstatus/status" "sigs.k8s.io/kustomize/kstatus/wait" "sigs.k8s.io/kustomize/kyaml/kio" ) +// GetWaitRunner return a command WaitRunner. func GetWaitRunner() *WaitRunner { r := &WaitRunner{} c := &cobra.Command{ - Use: "wait", - Short: "Wait", - RunE: r.runE, + Use: "wait DIR...", + Short: commands.WaitShort, + Long: commands.WaitLong, + Example: commands.WaitExamples, + RunE: r.runE, } c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true, "also print resources from subpackages.") @@ -35,6 +41,8 @@ func WaitCommand() *cobra.Command { return GetWaitRunner().Command } +// WaitRunner captures the parameters for the command and contains +// the run function. type WaitRunner struct { IncludeSubpackages bool Interval time.Duration @@ -42,6 +50,9 @@ type WaitRunner struct { Command *cobra.Command } +// runE implements the logic of the command and will call the Wait command in the wait +// package, use a ResourceStatusCollector to capture the events from the channel, and the +// TablePrinter to display the information. func (r *WaitRunner) runE(c *cobra.Command, args []string) error { ctx := context.Background() client, err := getClient() @@ -98,6 +109,9 @@ func (r *WaitRunner) runE(c *cobra.Command, args []string) error { return nil } +// ResourceStatusCollector captures the latest state seen for all resources +// based on the events from the Wait channel. This is used by the TablePrinter +// to display status for all resources. type ResourceStatusCollector struct { mux sync.RWMutex @@ -105,6 +119,8 @@ type ResourceStatusCollector struct { ResourceStatuses []*ResourceStatus } +// updateResourceStatus takes the given event and update the status info +// in the ResourceStatusCollector. func (r *ResourceStatusCollector) updateResourceStatus(msg wait.Event) { r.mux.Lock() defer r.mux.Unlock() @@ -121,18 +137,23 @@ func (r *ResourceStatusCollector) updateResourceStatus(msg wait.Event) { } } +// updateAggregateStatus sets the aggregate status of the ResourceStatusCollector to the +// given value. func (r *ResourceStatusCollector) updateAggregateStatus(aggregateStatus status.Status) { r.mux.Lock() defer r.mux.Unlock() r.AggregateStatus = aggregateStatus } +// ResourceStatus contains the status information for a single resource. type ResourceStatus struct { Identifier wait.ResourceIdentifier Status status.Status Message string } +// newResourceStatusCollector creates a new ResourceStatusCollector with the given +// resources and sets the status for all of them to Unknown. func newResourceStatusCollector(identifiers []wait.ResourceIdentifier) *ResourceStatusCollector { var statuses []*ResourceStatus @@ -150,10 +171,16 @@ func newResourceStatusCollector(identifiers []wait.ResourceIdentifier) *Resource } } +// CollectorStatusInfo is a wrapper around the ResourceStatusCollector +// to make it adhere to the interface of the TableWriter. type CollectorStatusInfo struct { Collector *ResourceStatusCollector } +// CurrentStatus implements the interface for the TableWriter and +// returns a copy of the current status of the resources in the +// ResourceStatusCollector. This is done to make sure the TableWriter +// does not have to deal with synchronization when accessing the data. func (f CollectorStatusInfo) CurrentStatus() StatusData { f.Collector.mux.RLock() defer f.Collector.mux.RUnlock() diff --git a/cmd/resource/status/docs/commands/events.md b/cmd/resource/status/docs/commands/events.md new file mode 100644 index 000000000..c8c7e5515 --- /dev/null +++ b/cmd/resource/status/docs/commands/events.md @@ -0,0 +1,22 @@ +## events + +[Alpha] Poll the cluster until all provided resources have become Current and list the status change events. + +### Synopsis + +[Alpha] Poll the cluster for the state of all the provided resources until either they have all become +Current or the timeout is reached. The output will be status change events. + +The list of resources which should be polled are provided as manifests either on the filesystem or +on StdIn. + + DIR: + Path to local directory. If not provided, input is expected on StdIn. + +### Examples + + # Read resources from the filesystem and wait up to 1 minute for all of them to become Current + resource status events my-dir/ + + # Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current + kubectl get all --all-namespaces -oyaml | resource status events --timeout=5m diff --git a/cmd/resource/status/docs/commands/fetch.md b/cmd/resource/status/docs/commands/fetch.md new file mode 100644 index 000000000..8d9aee546 --- /dev/null +++ b/cmd/resource/status/docs/commands/fetch.md @@ -0,0 +1,21 @@ +## fetch + +[Alpha] Fetch the state of the provided resources from the cluster and display status in a table. + +### Synopsis + +[Alpha] Fetches the state of all provided resources from the cluster and displays the status in +a table. + +The list of resources are provided as manifests either on the filesystem or on StdIn. + + DIR: + Path to local directory. + +### Examples + + # Read resources from the filesystem and wait up to 1 minute for all of them to become Current + resource status fetch my-dir/ + + # Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current + kubectl get all --all-namespaces -oyaml | resource status fetch diff --git a/cmd/resource/status/docs/commands/wait.md b/cmd/resource/status/docs/commands/wait.md new file mode 100644 index 000000000..8f2b0062e --- /dev/null +++ b/cmd/resource/status/docs/commands/wait.md @@ -0,0 +1,22 @@ +## wait + +[Alpha] Poll the cluster until all provided resources have become Current and display progress in a table. + +### Synopsis + +[Alpha] Poll the cluster for the state of all the provided resources until either they have all become +Current or the timeout is reached. The output will be presented as a table. + +The list of resources which should be polled are provided as manifests either on the filesystem or +on StdIn. + + DIR: + Path to local directory. If not provided, input is expected on StdIn. + +### Examples + + # Read resources from the filesystem and wait up to 1 minute for all of them to become Current + resource status wait my-dir/ + + # Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current + kubectl get all --all-namespaces -oyaml | resource status wait --timeout=5m diff --git a/cmd/resource/status/generateddocs/commands/docs.go b/cmd/resource/status/generateddocs/commands/docs.go new file mode 100644 index 000000000..9ba6e621f --- /dev/null +++ b/cmd/resource/status/generateddocs/commands/docs.go @@ -0,0 +1,57 @@ + + +// Code generated by "mdtogo"; DO NOT EDIT. +package commands + +var EventsShort=`[Alpha] Poll the cluster until all provided resources have become Current and list the status change events.` +var EventsLong=` +[Alpha] Poll the cluster for the state of all the provided resources until either they have all become +Current or the timeout is reached. The output will be status change events. + +The list of resources which should be polled are provided as manifests either on the filesystem or +on StdIn. + + DIR: + Path to local directory. If not provided, input is expected on StdIn. +` +var EventsExamples=` + # Read resources from the filesystem and wait up to 1 minute for all of them to become Current + resource status events my-dir/ + + # Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current + kubectl get all --all-namespaces -oyaml | resource status events --timeout=5m` + +var FetchShort=`[Alpha] Fetch the state of the provided resources from the cluster and display status in a table.` +var FetchLong=` +[Alpha] Fetches the state of all provided resources from the cluster and displays the status in +a table. + +The list of resources are provided as manifests either on the filesystem or on StdIn. + + DIR: + Path to local directory. +` +var FetchExamples=` + # Read resources from the filesystem and wait up to 1 minute for all of them to become Current + resource status fetch my-dir/ + + # Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current + kubectl get all --all-namespaces -oyaml | resource status fetch` + +var WaitShort=`[Alpha] Poll the cluster until all provided resources have become Current and display progress in a table. ` +var WaitLong=` +[Alpha] Poll the cluster for the state of all the provided resources until either they have all become +Current or the timeout is reached. The output will be presented as a table. + +The list of resources which should be polled are provided as manifests either on the filesystem or +on StdIn. + + DIR: + Path to local directory. If not provided, input is expected on StdIn. +` +var WaitExamples=` + # Read resources from the filesystem and wait up to 1 minute for all of them to become Current + resource status wait my-dir/ + + # Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current + kubectl get all --all-namespaces -oyaml | resource status wait --timeout=5m` diff --git a/cmd/resource/status/status.go b/cmd/resource/status/status.go index 6e6b3beda..ff729a2a9 100644 --- a/cmd/resource/status/status.go +++ b/cmd/resource/status/status.go @@ -1,3 +1,8 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +//go:generate $GOBIN/mdtogo docs/commands generateddocs/commands --license=none + package status import ( @@ -8,7 +13,7 @@ import ( func StatusCommand() *cobra.Command { var status = &cobra.Command{ Use: "status", - Short: "status reference command", + Short: "[Alpha] Commands for working with resource status.", } status.AddCommand(cmd.FetchCommand()) diff --git a/kyaml/fieldmeta/fieldmeta.go b/kyaml/fieldmeta/fieldmeta.go index 3a9742654..3ed5fc73c 100644 --- a/kyaml/fieldmeta/fieldmeta.go +++ b/kyaml/fieldmeta/fieldmeta.go @@ -22,7 +22,8 @@ type FieldMeta struct { type XKustomize struct { SetBy string `yaml:"setBy,omitempty" json:"setBy,omitempty"` - PartialFieldSetters []PartialFieldSetter `yaml:"partialFieldSetters" json:"partialFieldSetters"` + PartialFieldSetters []PartialFieldSetter `yaml:"partialSetters,omitempty" json:"partialSetters,omitempty"` + FieldSetter *PartialFieldSetter `yaml:"setter,omitempty" json:"setter,omitempty"` } // PartialFieldSetter defines how to set part of a field rather than the full field diff --git a/kyaml/setters/addyaml.go b/kyaml/setters/addyaml.go index 22aec3696..0a70fa84a 100644 --- a/kyaml/setters/addyaml.go +++ b/kyaml/setters/addyaml.go @@ -29,6 +29,9 @@ type customFieldSetter struct { Type string + // Partial will create a partial setter if set to true + Partial bool + // currentFieldName is the name of the current field being processed currentFieldName string } @@ -66,11 +69,6 @@ func (m *customFieldSetter) Filter(object *yaml.RNode) (*yaml.RNode, error) { } func (m *customFieldSetter) create(field *yaml.RNode) error { - // doesn't match the supplied value - if !strings.Contains(field.YNode().Value, m.Setter.Value) { - return nil - } - fm := fieldmeta.FieldMeta{} if err := fm.Read(field); err != nil { return errors.Wrap(err) @@ -83,20 +81,35 @@ func (m *customFieldSetter) create(field *yaml.RNode) error { fm.Extensions.SetBy = m.SetBy fm.Schema.Type = []string{m.Type} - found := false - for i := range fm.Extensions.PartialFieldSetters { - s := fm.Extensions.PartialFieldSetters[i] - if s.Name == m.Setter.Name { - // update the setter if we find it - found = true - fm.Extensions.PartialFieldSetters[i] = m.Setter - break + if !m.Partial { + // doesn't match the supplied value + if field.YNode().Value != m.Setter.Value { + return nil + } + // full setter + fm.Extensions.FieldSetter = &m.Setter + fm.Extensions.PartialFieldSetters = nil + } else { + // doesn't match the supplied value + if !strings.Contains(field.YNode().Value, m.Setter.Value) { + return nil + } + found := false + for i := range fm.Extensions.PartialFieldSetters { + s := fm.Extensions.PartialFieldSetters[i] + if s.Name == m.Setter.Name { + // update the setter if we find it + found = true + fm.Extensions.PartialFieldSetters[i] = m.Setter + break + } + } + if !found { + // add the setter if it wasn't found + fm.Extensions.PartialFieldSetters = append(fm.Extensions.PartialFieldSetters, m.Setter) } } - if !found { - // add the setter if it wasn't found - fm.Extensions.PartialFieldSetters = append(fm.Extensions.PartialFieldSetters, m.Setter) - } + if err := fm.Write(field); err != nil { return errors.Wrap(err) } diff --git a/kyaml/setters/dokio.go b/kyaml/setters/dokio.go index 182a73122..47f808ad4 100644 --- a/kyaml/setters/dokio.go +++ b/kyaml/setters/dokio.go @@ -30,10 +30,11 @@ type PerformSetters struct { func (s *PerformSetters) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) { for i := range input { - p := &partialFieldSetter{ + p := &fieldSetter{ Name: s.Name, Value: s.Value, Description: s.Description, + SetBy: s.SetBy, } if err := input[i].PipeE(p); err != nil { return nil, err diff --git a/kyaml/setters/doyaml.go b/kyaml/setters/doyaml.go index 69d64acc2..dd35fea61 100644 --- a/kyaml/setters/doyaml.go +++ b/kyaml/setters/doyaml.go @@ -11,10 +11,10 @@ import ( "sigs.k8s.io/kustomize/kyaml/yaml" ) -var _ yaml.Filter = &partialFieldSetter{} +var _ yaml.Filter = &fieldSetter{} -// partialFieldSetter sets part of a field value. -type partialFieldSetter struct { +// fieldSetter sets part or all of a field value. +type fieldSetter struct { // Name is the name of the setter to perform. Name string @@ -32,7 +32,7 @@ type partialFieldSetter struct { } // Filter implements yaml.Filter -func (fs *partialFieldSetter) Filter(object *yaml.RNode) (*yaml.RNode, error) { +func (fs *fieldSetter) Filter(object *yaml.RNode) (*yaml.RNode, error) { switch object.YNode().Kind { case yaml.DocumentNode: // Document is the root of the object and always contains 1 node @@ -49,7 +49,7 @@ func (fs *partialFieldSetter) Filter(object *yaml.RNode) (*yaml.RNode, error) { }) case yaml.ScalarNode: // Check if there is a setter matching the name - s, f, err := fs.findPartialSetter(object) + s, f, partial, err := fs.findSetter(object) if err != nil { return nil, err } @@ -58,19 +58,19 @@ func (fs *partialFieldSetter) Filter(object *yaml.RNode) (*yaml.RNode, error) { return object, nil } // set the field value - return object, fs.set(object, s, f) + return object, fs.set(object, s, f, partial) default: return object, nil } } // findPartialSetter finds the setter matching the name if one exists -func (fs *partialFieldSetter) findPartialSetter(field *yaml.RNode) ( - *fieldmeta.PartialFieldSetter, *fieldmeta.FieldMeta, error) { +func (fs *fieldSetter) findSetter(field *yaml.RNode) ( + *fieldmeta.PartialFieldSetter, *fieldmeta.FieldMeta, bool, error) { // check if there are any substitutions for this field var fm = &fieldmeta.FieldMeta{} if err := fm.Read(field); err != nil { - return nil, nil, err + return nil, nil, false, err } if fs.SetBy != "" { fm.Extensions.SetBy = fs.SetBy @@ -79,18 +79,23 @@ func (fs *partialFieldSetter) findPartialSetter(field *yaml.RNode) ( fm.Schema.Description = fs.Description } + if fm.Extensions.FieldSetter != nil && fm.Extensions.FieldSetter.Name == fs.Name { + return fm.Extensions.FieldSetter, fm, false, nil + } + // check if there is a matching substitution for i := range fm.Extensions.PartialFieldSetters { if fm.Extensions.PartialFieldSetters[i].Name == fs.Name { - return &fm.Extensions.PartialFieldSetters[i], fm, nil + return &fm.Extensions.PartialFieldSetters[i], fm, true, nil } } - return nil, nil, nil + return nil, nil, false, nil } // set performs the substitution for the given field, substitution, and metadata -func (fs *partialFieldSetter) set( - field *yaml.RNode, s *fieldmeta.PartialFieldSetter, f *fieldmeta.FieldMeta) error { +func (fs *fieldSetter) set( + field *yaml.RNode, s *fieldmeta.PartialFieldSetter, + f *fieldmeta.FieldMeta, partial bool) error { if s.Value == fs.Value || !strings.Contains(field.YNode().Value, s.Value) { // no substitutions necessary -- already substituted or doesn't have the set value // which acts as a marker @@ -100,8 +105,13 @@ func (fs *partialFieldSetter) set( // record that the config has been modified fs.Count++ - // replace the current value with the new value - field.YNode().Value = strings.ReplaceAll(field.YNode().Value, s.Value, fs.Value) + if !partial { + // full setter + field.YNode().Value = fs.Value + } else { + // replace the current value with the new value + field.YNode().Value = strings.ReplaceAll(field.YNode().Value, s.Value, fs.Value) + } // be sure to set the tag to the matching type so the yaml doesn't incorrectly quote //integers or booleans as strings diff --git a/kyaml/setters/lookupyaml.go b/kyaml/setters/lookupyaml.go index 425b58a62..ecc5923ce 100644 --- a/kyaml/setters/lookupyaml.go +++ b/kyaml/setters/lookupyaml.go @@ -54,6 +54,22 @@ func (ls *lookupSetters) lookup(field *yaml.RNode) error { return err } + if fm.Extensions.FieldSetter != nil { + if ls.Name != "" && ls.Name != fm.Extensions.FieldSetter.Name { + // skip this setter, it doesn't match the specified setter + return nil + } + // full setter + ls.Setters = append(ls.Setters, setter{ + PartialFieldSetter: *fm.Extensions.FieldSetter, + Description: fm.Schema.Description, + Type: fm.Schema.Type[0], + SetBy: fm.Extensions.SetBy, + }) + return nil + } + + // partial setters for i := range fm.Extensions.PartialFieldSetters { if ls.Name != "" && ls.Name != fm.Extensions.PartialFieldSetters[i].Name { // skip this setter diff --git a/releasing/VERSIONS b/releasing/VERSIONS index 1ecbb0a4a..56918ec0e 100644 --- a/releasing/VERSIONS +++ b/releasing/VERSIONS @@ -6,7 +6,7 @@ # kyaml version export kyaml_major=0 export kyaml_minor=0 -export kyaml_patch=3 +export kyaml_patch=4 # kustomize api version export api_major=0 @@ -16,7 +16,7 @@ export api_patch=1 # cmd/config version export cmd_config_major=0 export cmd_config_minor=0 -export cmd_config_patch=3 +export cmd_config_patch=4 # cmd/kubectl version export cmd_kubectl_major=0 diff --git a/releasing/releasemodule.sh b/releasing/releasemodule.sh index 888c18b8c..c73661680 100755 --- a/releasing/releasemodule.sh +++ b/releasing/releasemodule.sh @@ -55,7 +55,7 @@ function releaseModule { git tag -a $tag -m "Release $tag on branch $branch" git push upstream $tag else - printf "\nSkipping push binary $binary -- run with NO_DRY_RUN=true to push the release.\n\n" + printf "\nSkipping push module $module -- run with NO_DRY_RUN=true to push the release.\n\n" fi # cleanup release artifacts