mirror of https://github.com/kubernetes/kops.git
Merge pull request #11745 from johngmyers/remove-option
Allow unsetting fields from the command line
This commit is contained in:
commit
bfd0b6d9ba
|
@ -50,6 +50,9 @@ go_library(
|
|||
"toolbox_dump.go",
|
||||
"toolbox_instance_selector.go",
|
||||
"toolbox_template.go",
|
||||
"unset.go",
|
||||
"unset_cluster.go",
|
||||
"unset_instancegroups.go",
|
||||
"update.go",
|
||||
"update_cluster.go",
|
||||
"upgrade.go",
|
||||
|
|
|
@ -70,8 +70,10 @@ type CreateClusterOptions struct {
|
|||
// SSHPublicKeys is a map of the SSH public keys we should configure; required on AWS, not required on GCE
|
||||
SSHPublicKeys map[string][]byte
|
||||
|
||||
// Overrides allows settings values direct in the spec
|
||||
// Overrides allows setting values directly in the spec.
|
||||
Overrides []string
|
||||
// Unsets allows unsetting values directly in the spec.
|
||||
Unsets []string
|
||||
|
||||
// CloudLabels are cloud-provider-level tags for instance groups and volumes.
|
||||
CloudLabels string
|
||||
|
@ -301,6 +303,7 @@ func NewCmdCreateCluster(f *util.Factory, out io.Writer) *cobra.Command {
|
|||
|
||||
if featureflag.SpecOverrideFlag.Enabled() {
|
||||
cmd.Flags().StringSliceVar(&options.Overrides, "override", options.Overrides, "Directly configure values in the spec")
|
||||
cmd.Flags().StringSliceVar(&options.Unsets, "unset", options.Unsets, "Directly unset values in the spec")
|
||||
}
|
||||
|
||||
// GCE flags
|
||||
|
@ -510,6 +513,9 @@ func RunCreateCluster(ctx context.Context, f *util.Factory, out io.Writer, c *Cr
|
|||
cluster.Spec.MasterPublicName = c.MasterPublicName
|
||||
}
|
||||
|
||||
if err := commands.UnsetClusterFields(c.Unsets, cluster); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := commands.SetClusterFields(c.Overrides, cluster); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -150,6 +150,7 @@ func NewCmdRoot(f *util.Factory, out io.Writer) *cobra.Command {
|
|||
cmd.AddCommand(NewCmdRollingUpdate(f, out))
|
||||
cmd.AddCommand(NewCmdSet(f, out))
|
||||
cmd.AddCommand(NewCmdToolbox(f, out))
|
||||
cmd.AddCommand(NewCmdUnset(f, out))
|
||||
cmd.AddCommand(NewCmdValidate(f, out))
|
||||
cmd.AddCommand(NewCmdVersion(f, out))
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ var (
|
|||
setExample = templates.Examples(i18n.T(`
|
||||
# Set cluster to run kubernetes version 1.17.0
|
||||
kops set cluster k8s-cluster.example.com spec.kubernetesVersion=1.17.0
|
||||
kops set instancegroup --name k8s-cluster.example.com nodes spec.maxSize=4
|
||||
kops set instancegroup --name k8s-cluster.example.com nodes-1a spec.maxSize=4
|
||||
`))
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed 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 main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kops/cmd/kops/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
unsetLong = templates.LongDesc(i18n.T(`Unset a configuration field.
|
||||
|
||||
kops unset does not update the cloud resources; to apply the changes use "kops update cluster".
|
||||
`))
|
||||
|
||||
unsetExample = templates.Examples(i18n.T(`
|
||||
kops unset cluster k8s-cluster.example.com spec.iam.allowContainerRegistry
|
||||
kops unset instancegroup --name k8s-cluster.example.com nodes-1a spec.maxSize
|
||||
`))
|
||||
)
|
||||
|
||||
func NewCmdUnset(f *util.Factory, out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "unset",
|
||||
Short: i18n.T("Unset fields on clusters and other resources."),
|
||||
Long: unsetLong,
|
||||
Example: unsetExample,
|
||||
}
|
||||
|
||||
// create subcommands
|
||||
cmd.AddCommand(NewCmdUnsetCluster(f, out))
|
||||
cmd.AddCommand(NewCmdUnsetInstancegroup(f, out))
|
||||
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
|
||||
"k8s.io/kops/cmd/kops/util"
|
||||
"k8s.io/kops/pkg/commands"
|
||||
)
|
||||
|
||||
var (
|
||||
unsetClusterLong = templates.LongDesc(i18n.T(`Unset a cluster field value.
|
||||
|
||||
This command changes the desired cluster configuration in the registry.
|
||||
|
||||
kops unset does not update the cloud resources; to apply the changes use "kops update cluster".`))
|
||||
|
||||
unsetClusterExample = templates.Examples(i18n.T(`
|
||||
kops unset cluster k8s-cluster.example.com spec.iam.allowContainerRegistry
|
||||
`))
|
||||
)
|
||||
|
||||
// NewCmdUnsetCluster builds a cobra command for the kops unset cluster command
|
||||
func NewCmdUnsetCluster(f *util.Factory, out io.Writer) *cobra.Command {
|
||||
options := &commands.UnsetClusterOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "cluster",
|
||||
Short: i18n.T("Unset cluster fields."),
|
||||
Long: unsetClusterLong,
|
||||
Example: unsetClusterExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.TODO()
|
||||
|
||||
for i, arg := range args {
|
||||
if i == 0 && !strings.HasPrefix(arg, "spec.") && !strings.HasPrefix(arg, "cluster.") {
|
||||
options.ClusterName = arg
|
||||
} else {
|
||||
options.Fields = append(options.Fields, arg)
|
||||
}
|
||||
}
|
||||
|
||||
if options.ClusterName == "" {
|
||||
options.ClusterName = rootCommand.ClusterName()
|
||||
}
|
||||
|
||||
if err := commands.RunUnsetCluster(ctx, f, cmd, out, options); err != nil {
|
||||
exitWithError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
|
||||
"k8s.io/kops/cmd/kops/util"
|
||||
"k8s.io/kops/pkg/commands"
|
||||
)
|
||||
|
||||
var (
|
||||
unsetInstancegroupLong = templates.LongDesc(i18n.T(`Unset an instance group field value.
|
||||
|
||||
This command changes the desired instance group configuration in the registry.
|
||||
|
||||
kops unset does not update the cloud resources; to apply the changes use "kops update cluster".`))
|
||||
|
||||
unsetInstancegroupExample = templates.Examples(i18n.T(`
|
||||
# Set instance group to run default image
|
||||
kops unset instancegroup --name k8s-cluster.example.com nodes spec.image
|
||||
`))
|
||||
)
|
||||
|
||||
// NewCmdUnsetInstancegroup builds a cobra command for the kops set instancegroup command.
|
||||
func NewCmdUnsetInstancegroup(f *util.Factory, out io.Writer) *cobra.Command {
|
||||
options := &commands.UnsetInstanceGroupOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "instancegroup",
|
||||
Aliases: []string{"instancegroups", "ig"},
|
||||
Short: i18n.T("Unset instancegroup fields."),
|
||||
Long: unsetInstancegroupLong,
|
||||
Example: unsetInstancegroupExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.TODO()
|
||||
|
||||
for i, arg := range args {
|
||||
if i == 0 && !strings.HasPrefix(arg, "spec.") && !strings.HasPrefix(arg, "instancegroup.") {
|
||||
options.InstanceGroupName = arg
|
||||
} else {
|
||||
options.Fields = append(options.Fields, arg)
|
||||
}
|
||||
}
|
||||
|
||||
options.ClusterName = rootCommand.ClusterName()
|
||||
|
||||
if err := commands.RunUnsetInstancegroup(ctx, f, cmd, out, options); err != nil {
|
||||
exitWithError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
|
@ -49,6 +49,7 @@ kOps is Kubernetes Operations.
|
|||
* [kops rolling-update](kops_rolling-update.md) - Rolling update a cluster.
|
||||
* [kops set](kops_set.md) - Set fields on clusters and other resources.
|
||||
* [kops toolbox](kops_toolbox.md) - Misc infrequently used commands.
|
||||
* [kops unset](kops_unset.md) - Unset fields on clusters and other resources.
|
||||
* [kops update](kops_update.md) - Update a cluster.
|
||||
* [kops upgrade](kops_upgrade.md) - Upgrade a kubernetes cluster.
|
||||
* [kops validate](kops_validate.md) - Validate a kOps cluster.
|
||||
|
|
|
@ -16,7 +16,7 @@ Set a configuration field.
|
|||
```
|
||||
# Set cluster to run kubernetes version 1.17.0
|
||||
kops set cluster k8s-cluster.example.com spec.kubernetesVersion=1.17.0
|
||||
kops set instancegroup --name k8s-cluster.example.com nodes spec.maxSize=4
|
||||
kops set instancegroup --name k8s-cluster.example.com nodes-1a spec.maxSize=4
|
||||
```
|
||||
|
||||
### Options
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
|
||||
<!--- This file is automatically generated by make gen-cli-docs; changes should be made in the go CLI command code (under cmd/kops) -->
|
||||
|
||||
## kops unset
|
||||
|
||||
Unset fields on clusters and other resources.
|
||||
|
||||
### Synopsis
|
||||
|
||||
Unset a configuration field.
|
||||
|
||||
kops unset does not update the cloud resources; to apply the changes use "kops update cluster".
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
kops unset cluster k8s-cluster.example.com spec.iam.allowContainerRegistry
|
||||
kops unset instancegroup --name k8s-cluster.example.com nodes-1a spec.maxSize
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for unset
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--add_dir_header If true, adds the file directory to the header of the log messages
|
||||
--alsologtostderr log to standard error as well as files
|
||||
--config string yaml config file (default is $HOME/.kops.yaml)
|
||||
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
|
||||
--log_dir string If non-empty, write log files in this directory
|
||||
--log_file string If non-empty, use this log file
|
||||
--log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
|
||||
--logtostderr log to standard error instead of files (default true)
|
||||
--name string Name of cluster. Overrides KOPS_CLUSTER_NAME environment variable
|
||||
--one_output If true, only write logs to their native severity level (vs also writing to each lower severity level)
|
||||
--skip_headers If true, avoid header prefixes in the log messages
|
||||
--skip_log_headers If true, avoid headers when opening log files
|
||||
--state string Location of state storage (kops 'config' file). Overrides KOPS_STATE_STORE environment variable
|
||||
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
|
||||
-v, --v Level number for the log level verbosity
|
||||
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [kops](kops.md) - kOps is Kubernetes Operations.
|
||||
* [kops unset cluster](kops_unset_cluster.md) - Unset cluster fields.
|
||||
* [kops unset instancegroup](kops_unset_instancegroup.md) - Unset instancegroup fields.
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
|
||||
<!--- This file is automatically generated by make gen-cli-docs; changes should be made in the go CLI command code (under cmd/kops) -->
|
||||
|
||||
## kops unset cluster
|
||||
|
||||
Unset cluster fields.
|
||||
|
||||
### Synopsis
|
||||
|
||||
Unset a cluster field value.
|
||||
|
||||
This command changes the desired cluster configuration in the registry.
|
||||
|
||||
kops unset does not update the cloud resources; to apply the changes use "kops update cluster".
|
||||
|
||||
```
|
||||
kops unset cluster [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
kops unset cluster k8s-cluster.example.com spec.iam.allowContainerRegistry
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for cluster
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--add_dir_header If true, adds the file directory to the header of the log messages
|
||||
--alsologtostderr log to standard error as well as files
|
||||
--config string yaml config file (default is $HOME/.kops.yaml)
|
||||
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
|
||||
--log_dir string If non-empty, write log files in this directory
|
||||
--log_file string If non-empty, use this log file
|
||||
--log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
|
||||
--logtostderr log to standard error instead of files (default true)
|
||||
--name string Name of cluster. Overrides KOPS_CLUSTER_NAME environment variable
|
||||
--one_output If true, only write logs to their native severity level (vs also writing to each lower severity level)
|
||||
--skip_headers If true, avoid header prefixes in the log messages
|
||||
--skip_log_headers If true, avoid headers when opening log files
|
||||
--state string Location of state storage (kops 'config' file). Overrides KOPS_STATE_STORE environment variable
|
||||
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
|
||||
-v, --v Level number for the log level verbosity
|
||||
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [kops unset](kops_unset.md) - Unset fields on clusters and other resources.
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
|
||||
<!--- This file is automatically generated by make gen-cli-docs; changes should be made in the go CLI command code (under cmd/kops) -->
|
||||
|
||||
## kops unset instancegroup
|
||||
|
||||
Unset instancegroup fields.
|
||||
|
||||
### Synopsis
|
||||
|
||||
Unset an instance group field value.
|
||||
|
||||
This command changes the desired instance group configuration in the registry.
|
||||
|
||||
kops unset does not update the cloud resources; to apply the changes use "kops update cluster".
|
||||
|
||||
```
|
||||
kops unset instancegroup [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
# Set instance group to run default image
|
||||
kops unset instancegroup --name k8s-cluster.example.com nodes spec.image
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for instancegroup
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--add_dir_header If true, adds the file directory to the header of the log messages
|
||||
--alsologtostderr log to standard error as well as files
|
||||
--config string yaml config file (default is $HOME/.kops.yaml)
|
||||
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
|
||||
--log_dir string If non-empty, write log files in this directory
|
||||
--log_file string If non-empty, use this log file
|
||||
--log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
|
||||
--logtostderr log to standard error instead of files (default true)
|
||||
--name string Name of cluster. Overrides KOPS_CLUSTER_NAME environment variable
|
||||
--one_output If true, only write logs to their native severity level (vs also writing to each lower severity level)
|
||||
--skip_headers If true, avoid header prefixes in the log messages
|
||||
--skip_log_headers If true, avoid headers when opening log files
|
||||
--state string Location of state storage (kops 'config' file). Overrides KOPS_STATE_STORE environment variable
|
||||
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
|
||||
-v, --v Level number for the log level verbosity
|
||||
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [kops unset](kops_unset.md) - Unset fields on clusters and other resources.
|
||||
|
|
@ -7,6 +7,8 @@ go_library(
|
|||
"helpers_readwrite.go",
|
||||
"set_cluster.go",
|
||||
"set_instancegroups.go",
|
||||
"unset_cluster.go",
|
||||
"unset_instancegroups.go",
|
||||
"version.go",
|
||||
],
|
||||
importpath = "k8s.io/kops/pkg/commands",
|
||||
|
@ -35,6 +37,8 @@ go_test(
|
|||
srcs = [
|
||||
"set_cluster_test.go",
|
||||
"set_instancegroups_test.go",
|
||||
"unset_cluster_test.go",
|
||||
"unset_instancegroups_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed 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 commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
"k8s.io/kops/cmd/kops/util"
|
||||
api "k8s.io/kops/pkg/apis/kops"
|
||||
"k8s.io/kops/pkg/featureflag"
|
||||
"k8s.io/kops/util/pkg/reflectutils"
|
||||
)
|
||||
|
||||
type UnsetClusterOptions struct {
|
||||
Fields []string
|
||||
ClusterName string
|
||||
}
|
||||
|
||||
// RunUnsetCluster implements the unset cluster command logic
|
||||
func RunUnsetCluster(ctx context.Context, f *util.Factory, cmd *cobra.Command, out io.Writer, options *UnsetClusterOptions) error {
|
||||
if !featureflag.SpecOverrideFlag.Enabled() {
|
||||
return fmt.Errorf("unset cluster command is current feature gated; set `export KOPS_FEATURE_FLAGS=SpecOverrideFlag`")
|
||||
}
|
||||
|
||||
if options.ClusterName == "" {
|
||||
return field.Required(field.NewPath("clusterName"), "Cluster name is required")
|
||||
}
|
||||
|
||||
clientset, err := f.Clientset()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cluster, err := clientset.GetCluster(ctx, options.ClusterName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
instanceGroups, err := ReadAllInstanceGroups(ctx, clientset, cluster)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := UnsetClusterFields(options.Fields, cluster); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := UpdateCluster(ctx, clientset, cluster, instanceGroups); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsetClusterFields unsets field values in the cluster
|
||||
func UnsetClusterFields(fields []string, cluster *api.Cluster) error {
|
||||
for _, field := range fields {
|
||||
key := strings.TrimPrefix(field, "cluster.")
|
||||
|
||||
if err := reflectutils.Unset(cluster, key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,455 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed 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 commands
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kops/pkg/apis/kops"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
)
|
||||
|
||||
func TestUnsetClusterBadInput(t *testing.T) {
|
||||
fields := []string{
|
||||
"bad-unset-input",
|
||||
}
|
||||
|
||||
err := UnsetClusterFields(fields, &kops.Cluster{})
|
||||
if err == nil {
|
||||
t.Errorf("expected a field parsing error, but received none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsetClusterFields(t *testing.T) {
|
||||
grid := []struct {
|
||||
Fields []string
|
||||
Input kops.Cluster
|
||||
Output kops.Cluster
|
||||
}{
|
||||
{
|
||||
Fields: []string{
|
||||
"spec.kubernetesVersion",
|
||||
"spec.kubelet.authorizationMode",
|
||||
"spec.kubelet.authenticationTokenWebhook",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
KubernetesVersion: "1.8.2",
|
||||
Kubelet: &kops.KubeletConfigSpec{
|
||||
AuthorizationMode: "Webhook",
|
||||
AuthenticationTokenWebhook: fi.Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Kubelet: &kops.KubeletConfigSpec{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{"spec.kubelet.authorizationMode"},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Kubelet: &kops.KubeletConfigSpec{
|
||||
AuthorizationMode: "Webhook",
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Kubelet: &kops.KubeletConfigSpec{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{"spec.kubelet.authenticationTokenWebhook"},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Kubelet: &kops.KubeletConfigSpec{
|
||||
AuthenticationTokenWebhook: fi.Bool(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Kubelet: &kops.KubeletConfigSpec{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{"spec.docker.selinuxEnabled"},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Docker: &kops.DockerConfig{
|
||||
SelinuxEnabled: fi.Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Docker: &kops.DockerConfig{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{"spec.kubernetesVersion"},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
KubernetesVersion: "v1.2.3",
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{"spec.masterPublicName"},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
MasterPublicName: "api.example.com",
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{"spec.kubeDNS.provider"},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
KubeDNS: &kops.KubeDNSConfig{
|
||||
Provider: "CoreDNS",
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
KubeDNS: &kops.KubeDNSConfig{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.nodePortAccess",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
NodePortAccess: []string{"10.0.0.0/8", "192.168.0.0/16"},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.etcdClusters[*].enableEtcdTLS",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
EtcdClusters: []kops.EtcdClusterSpec{
|
||||
{Name: "one", EnableEtcdTLS: true},
|
||||
{Name: "two", EnableEtcdTLS: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
EtcdClusters: []kops.EtcdClusterSpec{
|
||||
{Name: "one"},
|
||||
{Name: "two"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.etcdClusters",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
EtcdClusters: []kops.EtcdClusterSpec{
|
||||
{Name: "one", EnableTLSAuth: true},
|
||||
{Name: "two", EnableTLSAuth: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.etcdClusters[*].version",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
EtcdClusters: []kops.EtcdClusterSpec{
|
||||
{Name: "one", Version: "v3.2.1"},
|
||||
{Name: "two", Version: "v3.2.1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
EtcdClusters: []kops.EtcdClusterSpec{
|
||||
{Name: "one"},
|
||||
{Name: "two"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.etcdClusters[*].provider",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
EtcdClusters: []kops.EtcdClusterSpec{
|
||||
{Name: "one", Provider: kops.EtcdProviderTypeManager},
|
||||
{Name: "two", Provider: kops.EtcdProviderTypeManager},
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
EtcdClusters: []kops.EtcdClusterSpec{
|
||||
{Name: "one"},
|
||||
{Name: "two"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.etcdClusters[*]",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
EtcdClusters: []kops.EtcdClusterSpec{
|
||||
{Name: "one", Image: "etcd-manager:v1.2.3"},
|
||||
{Name: "two", Image: "etcd-manager:v1.2.3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
EtcdClusters: []kops.EtcdClusterSpec{
|
||||
{},
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.networking.cilium.ipam",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Networking: &kops.NetworkingSpec{
|
||||
Cilium: &kops.CiliumNetworkingSpec{
|
||||
Ipam: "on",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Networking: &kops.NetworkingSpec{
|
||||
Cilium: &kops.CiliumNetworkingSpec{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.networking.cilium.enableHostReachableServices",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Networking: &kops.NetworkingSpec{
|
||||
Cilium: &kops.CiliumNetworkingSpec{
|
||||
EnableHostReachableServices: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Networking: &kops.NetworkingSpec{
|
||||
Cilium: &kops.CiliumNetworkingSpec{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.networking.cilium.enableNodePort",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Networking: &kops.NetworkingSpec{
|
||||
Cilium: &kops.CiliumNetworkingSpec{
|
||||
EnableNodePort: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Networking: &kops.NetworkingSpec{
|
||||
Cilium: &kops.CiliumNetworkingSpec{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.networking.cilium.disableMasquerade",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Networking: &kops.NetworkingSpec{
|
||||
Cilium: &kops.CiliumNetworkingSpec{
|
||||
DisableMasquerade: fi.Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Networking: &kops.NetworkingSpec{
|
||||
Cilium: &kops.CiliumNetworkingSpec{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.kubeProxy.enabled",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
KubeProxy: &kops.KubeProxyConfig{
|
||||
Enabled: fi.Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
KubeProxy: &kops.KubeProxyConfig{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.networking.cilium.agentPrometheusPort",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Networking: &kops.NetworkingSpec{
|
||||
Cilium: &kops.CiliumNetworkingSpec{
|
||||
AgentPrometheusPort: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
Networking: &kops.NetworkingSpec{
|
||||
Cilium: &kops.CiliumNetworkingSpec{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, g := range grid {
|
||||
c := g.Input
|
||||
|
||||
err := UnsetClusterFields(g.Fields, &c)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error from unsetClusterFields %v: %v", g.Fields, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(c, g.Output) {
|
||||
t.Errorf("unexpected output from unsetClusterFields %v. expected=%v, actual=%v", g.Fields, g.Output, c)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsetCiliumFields(t *testing.T) {
|
||||
|
||||
grid := []struct {
|
||||
Fields []string
|
||||
Input kops.Cluster
|
||||
Output kops.Cluster
|
||||
}{
|
||||
{
|
||||
Fields: []string{
|
||||
"cluster.spec.networking.cilium.ipam",
|
||||
"cluster.spec.networking.cilium.enableNodePort",
|
||||
"cluster.spec.networking.cilium.disableMasquerade",
|
||||
"cluster.spec.kubeProxy.enabled",
|
||||
},
|
||||
Input: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
KubeProxy: &kops.KubeProxyConfig{
|
||||
Enabled: fi.Bool(false),
|
||||
},
|
||||
Networking: &kops.NetworkingSpec{
|
||||
Cilium: &kops.CiliumNetworkingSpec{
|
||||
Ipam: "eni",
|
||||
EnableNodePort: true,
|
||||
DisableMasquerade: fi.Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.Cluster{
|
||||
Spec: kops.ClusterSpec{
|
||||
KubeProxy: &kops.KubeProxyConfig{},
|
||||
Networking: &kops.NetworkingSpec{
|
||||
Cilium: &kops.CiliumNetworkingSpec{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, g := range grid {
|
||||
c := g.Input
|
||||
|
||||
err := UnsetClusterFields(g.Fields, &c)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error from unsetClusterFields %v: %v", g.Fields, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(c, g.Output) {
|
||||
t.Errorf("unexpected output from unsetClusterFields %v. expected=%v, actual=%v", g.Fields, g.Output, c)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed 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 commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
"k8s.io/kops/cmd/kops/util"
|
||||
api "k8s.io/kops/pkg/apis/kops"
|
||||
"k8s.io/kops/pkg/featureflag"
|
||||
"k8s.io/kops/util/pkg/reflectutils"
|
||||
)
|
||||
|
||||
// UnsetInstanceGroupOptions contains the options for unsetting configuration on an
|
||||
// instance group.
|
||||
type UnsetInstanceGroupOptions struct {
|
||||
Fields []string
|
||||
ClusterName string
|
||||
InstanceGroupName string
|
||||
}
|
||||
|
||||
// RunUnsetInstancegroup implements the unset instancegroup command logic.
|
||||
func RunUnsetInstancegroup(ctx context.Context, f *util.Factory, cmd *cobra.Command, out io.Writer, options *UnsetInstanceGroupOptions) error {
|
||||
if !featureflag.SpecOverrideFlag.Enabled() {
|
||||
return fmt.Errorf("unset instancegroup is currently feature gated; set `export KOPS_FEATURE_FLAGS=SpecOverrideFlag`")
|
||||
}
|
||||
|
||||
if options.ClusterName == "" {
|
||||
return field.Required(field.NewPath("clusterName"), "Cluster name is required")
|
||||
}
|
||||
if options.InstanceGroupName == "" {
|
||||
return field.Required(field.NewPath("instancegroupName"), "Instance Group name is required")
|
||||
}
|
||||
|
||||
clientset, err := f.Clientset()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cluster, err := clientset.GetCluster(ctx, options.ClusterName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
instanceGroups, err := ReadAllInstanceGroups(ctx, clientset, cluster)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var instanceGroupToUpdate *api.InstanceGroup
|
||||
for _, instanceGroup := range instanceGroups {
|
||||
if instanceGroup.GetName() == options.InstanceGroupName {
|
||||
instanceGroupToUpdate = instanceGroup
|
||||
}
|
||||
}
|
||||
if instanceGroupToUpdate == nil {
|
||||
return fmt.Errorf("unable to find instance group with name %q", options.InstanceGroupName)
|
||||
}
|
||||
|
||||
err = UnsetInstancegroupFields(options.Fields, instanceGroupToUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = UpdateInstanceGroup(ctx, clientset, cluster, instanceGroups, instanceGroupToUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsetInstancegroupFields sets field values in the instance group.
|
||||
func UnsetInstancegroupFields(fields []string, instanceGroup *api.InstanceGroup) error {
|
||||
for _, field := range fields {
|
||||
key := strings.TrimPrefix(field, "instancegroup.")
|
||||
|
||||
if err := reflectutils.Unset(instanceGroup, key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed 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 commands
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kops/pkg/apis/kops"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
)
|
||||
|
||||
func TestUnsetInstanceGroupsBadInput(t *testing.T) {
|
||||
fields := []string{
|
||||
"bad-unset-input",
|
||||
}
|
||||
|
||||
err := UnsetInstancegroupFields(fields, &kops.InstanceGroup{})
|
||||
if err == nil {
|
||||
t.Errorf("expected a field parsing error, but received none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsetInstanceGroupsFields(t *testing.T) {
|
||||
grid := []struct {
|
||||
Fields []string
|
||||
Input kops.InstanceGroup
|
||||
Output kops.InstanceGroup
|
||||
}{
|
||||
{
|
||||
Fields: []string{
|
||||
"spec.image",
|
||||
},
|
||||
Input: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{
|
||||
Image: "ami-test-1",
|
||||
},
|
||||
},
|
||||
Output: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"spec.machineType",
|
||||
},
|
||||
Input: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{
|
||||
MachineType: "m5.large",
|
||||
},
|
||||
},
|
||||
Output: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"spec.minSize",
|
||||
"spec.maxSize",
|
||||
},
|
||||
Input: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{
|
||||
MinSize: fi.Int32(1),
|
||||
MaxSize: fi.Int32(3),
|
||||
},
|
||||
},
|
||||
Output: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"spec.additionalSecurityGroups",
|
||||
},
|
||||
Input: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{
|
||||
AdditionalSecurityGroups: []string{
|
||||
"group1",
|
||||
"group2",
|
||||
"group3",
|
||||
},
|
||||
},
|
||||
},
|
||||
Output: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, g := range grid {
|
||||
ig := g.Input
|
||||
|
||||
err := UnsetInstancegroupFields(g.Fields, &ig)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error from unsetClusterFields %v: %v", g.Fields, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ig, g.Output) {
|
||||
t.Errorf("unexpected output from unsetClusterFields %v. expected=%v, actual=%v", g.Fields, g.Output, ig)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -169,3 +169,43 @@ func setPrimitive(v reflect.Value, newValue string) error {
|
|||
v.Set(newV)
|
||||
return nil
|
||||
}
|
||||
|
||||
func Unset(target interface{}, targetPath string) error {
|
||||
targetValue := reflect.ValueOf(target)
|
||||
|
||||
targetFieldPath, err := ParseFieldPath(targetPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse field path %q: %w", targetPath, err)
|
||||
}
|
||||
|
||||
var fieldUnset = false
|
||||
|
||||
visitor := func(path *FieldPath, field *reflect.StructField, v reflect.Value) error {
|
||||
if !targetFieldPath.HasPrefixMatch(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if targetFieldPath.Matches(path) {
|
||||
if !v.CanSet() {
|
||||
return fmt.Errorf("cannot unset field %q (marked immutable)", path)
|
||||
}
|
||||
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
fieldUnset = true
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err = ReflectRecursive(targetValue, visitor, &ReflectOptions{JSONNames: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !fieldUnset {
|
||||
return fmt.Errorf("field %s not found in %s", targetPath, BuildTypeName(reflect.TypeOf(target)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -207,7 +207,6 @@ func TestSetInvalidPath(t *testing.T) {
|
|||
grid := []struct {
|
||||
Name string
|
||||
Input string
|
||||
Expected string
|
||||
Path string
|
||||
Value string
|
||||
ExpectedError string
|
||||
|
@ -215,7 +214,6 @@ func TestSetInvalidPath(t *testing.T) {
|
|||
{
|
||||
Name: "setting with wildcard",
|
||||
Input: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ { 'image': 'hello-world' } ] } }",
|
||||
Path: "spec.containers[*].wrongImagePathName",
|
||||
Value: "hello-world",
|
||||
ExpectedError: "field spec.containers[*].wrongImagePathName not found in *fakeObject",
|
||||
|
@ -223,7 +221,6 @@ func TestSetInvalidPath(t *testing.T) {
|
|||
{
|
||||
Name: "creating missing objects",
|
||||
Input: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ { 'policy': { 'name': 'allowed' } } ] } }",
|
||||
Path: "spec.containers[0].policy.wrongPolicyName",
|
||||
Value: "allowed",
|
||||
ExpectedError: "field spec.containers[0].policy.wrongPolicyName not found in *fakeObject",
|
||||
|
@ -231,7 +228,6 @@ func TestSetInvalidPath(t *testing.T) {
|
|||
{
|
||||
Name: "set int",
|
||||
Input: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ { 'int': 123 } ] } }",
|
||||
Path: "spec.wrongNameContainers[0].int",
|
||||
Value: "123",
|
||||
ExpectedError: "field spec.wrongNameContainers[0].int not found in *fakeObject",
|
||||
|
@ -239,7 +235,6 @@ func TestSetInvalidPath(t *testing.T) {
|
|||
{
|
||||
Name: "set int32",
|
||||
Input: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ { 'int32': 123 } ] } }",
|
||||
Path: "spec.containers[0].int32100",
|
||||
Value: "123",
|
||||
ExpectedError: "field spec.containers[0].int32100 not found in *fakeObject",
|
||||
|
@ -247,7 +242,6 @@ func TestSetInvalidPath(t *testing.T) {
|
|||
{
|
||||
Name: "set int64",
|
||||
Input: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ { 'int64': 123 } ] } }",
|
||||
Path: "wrong.path.check",
|
||||
Value: "123",
|
||||
ExpectedError: "field wrong.path.check not found in *fakeObject",
|
||||
|
@ -276,3 +270,147 @@ func TestSetInvalidPath(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnset(t *testing.T) {
|
||||
grid := []struct {
|
||||
Name string
|
||||
Input string
|
||||
Expected string
|
||||
Path string
|
||||
}{
|
||||
{
|
||||
Name: "simple unsetting",
|
||||
Input: "{ 'spec': { 'containers': [ { 'image': 'hello-world' } ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Path: "spec.containers[0].image",
|
||||
},
|
||||
{
|
||||
Name: "unsetting with wildcard",
|
||||
Input: "{ 'spec': { 'containers': [ { 'image': 'hello-world' } ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Path: "spec.containers[*].image",
|
||||
},
|
||||
{
|
||||
Name: "uset int",
|
||||
Input: "{ 'spec': { 'containers': [ { 'int': 123 } ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Path: "spec.containers[0].int",
|
||||
},
|
||||
{
|
||||
Name: "unset int32",
|
||||
Input: "{ 'spec': { 'containers': [ { 'int32': 123 } ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Path: "spec.containers[0].int32",
|
||||
},
|
||||
{
|
||||
Name: "unset int64",
|
||||
Input: "{ 'spec': { 'containers': [ { 'int64': 123 } ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Path: "spec.containers[0].int64",
|
||||
},
|
||||
{
|
||||
Name: "unset int pointer",
|
||||
Input: "{ 'spec': { 'containers': [ { 'intPointer': 123 } ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Path: "spec.containers[0].intPointer",
|
||||
},
|
||||
{
|
||||
Name: "unset int32 pointer",
|
||||
Input: "{ 'spec': { 'containers': [ { 'int32Pointer': 123 } ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Path: "spec.containers[0].int32Pointer",
|
||||
},
|
||||
{
|
||||
Name: "unset int64 pointer",
|
||||
Input: "{ 'spec': { 'containers': [ { 'int64Pointer': 123 } ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Path: "spec.containers[0].int64Pointer",
|
||||
},
|
||||
{
|
||||
Name: "unset enum",
|
||||
Input: "{ 'spec': { 'containers': [ { 'enum': 'ABC' } ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ { 'enum': ''} ] } }",
|
||||
Path: "spec.containers[0].enum",
|
||||
},
|
||||
{
|
||||
Name: "unset enum slice",
|
||||
Input: "{ 'spec': { 'containers': [ { 'enumSlice': [ 'ABC', 'DEF' ] } ] } }",
|
||||
Expected: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Path: "spec.containers[0].enumSlice",
|
||||
},
|
||||
}
|
||||
|
||||
for _, g := range grid {
|
||||
g := g
|
||||
|
||||
t.Run(g.Name, func(t *testing.T) {
|
||||
|
||||
c := &fakeObject{}
|
||||
if err := json.Unmarshal(toJSON(g.Input), c); err != nil {
|
||||
t.Fatalf("failed to unmarshal input: %v", err)
|
||||
|
||||
}
|
||||
|
||||
if err := Unset(c, g.Path); err != nil {
|
||||
t.Fatalf("error from Unset: %v", err)
|
||||
}
|
||||
|
||||
// Changed in-place
|
||||
actual := c
|
||||
|
||||
expected := &fakeObject{}
|
||||
if err := json.Unmarshal(toJSON(g.Expected), expected); err != nil {
|
||||
t.Fatalf("failed to unmarshal expected: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(c, expected) {
|
||||
t.Fatalf("comparison failed; expected %+v, was %+v", expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsetInvalidPath(t *testing.T) {
|
||||
grid := []struct {
|
||||
Name string
|
||||
Input string
|
||||
Expected string
|
||||
Path string
|
||||
ExpectedError string
|
||||
}{
|
||||
{
|
||||
Name: "usetting with wildcard",
|
||||
Input: "{ 'spec': { 'containers': [ {} ] } }",
|
||||
Path: "spec.containers[*].wrongImagePathName",
|
||||
ExpectedError: "field spec.containers[*].wrongImagePathName not found in *fakeObject",
|
||||
},
|
||||
{
|
||||
Name: "missing objects",
|
||||
Input: "{ 'spec': { 'containers': [ { 'policy': { 'name': 'allowed' } } ] } }",
|
||||
Path: "spec.containers[0].policy.wrongPolicyName",
|
||||
ExpectedError: "field spec.containers[0].policy.wrongPolicyName not found in *fakeObject",
|
||||
},
|
||||
}
|
||||
|
||||
for _, g := range grid {
|
||||
g := g
|
||||
|
||||
t.Run(g.Name, func(t *testing.T) {
|
||||
|
||||
c := &fakeObject{}
|
||||
if err := json.Unmarshal(toJSON(g.Input), c); err != nil {
|
||||
t.Fatalf("failed to unmarshal input: %v", err)
|
||||
|
||||
}
|
||||
|
||||
err := Unset(c, g.Path)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error for invalid path %s", g.Path)
|
||||
}
|
||||
|
||||
if err.Error() != g.ExpectedError {
|
||||
t.Fatalf("Expected Error: %s\n Actual Error: %s", g.ExpectedError, err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue