mirror of https://github.com/kubernetes/kops.git
Add `set instancegroup` command
This change adds a new command and functionality for updating instance group configuration via command line arguments. This behavior mimics the `set cluster` command.
This commit is contained in:
parent
6b538c395c
commit
e90050f134
|
|
@ -43,6 +43,7 @@ go_library(
|
|||
"root.go",
|
||||
"set.go",
|
||||
"set_cluster.go",
|
||||
"set_instancegroups.go",
|
||||
"toolbox.go",
|
||||
"toolbox_convert_imported.go",
|
||||
"toolbox_dump.go",
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ 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 cluster k8s-cluster.example.com spec.kubernetesVersion=1.17.0
|
||||
kops set instancegroup k8s-cluster.example.com spec.maxSize=4
|
||||
`))
|
||||
)
|
||||
|
||||
|
|
@ -47,6 +48,7 @@ func NewCmdSet(f *util.Factory, out io.Writer) *cobra.Command {
|
|||
|
||||
// create subcommands
|
||||
cmd.AddCommand(NewCmdSetCluster(f, out))
|
||||
cmd.AddCommand(NewCmdSetInstancegroup(f, out))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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 (
|
||||
setInstancegroupLong = templates.LongDesc(i18n.T(`Set an instance group field value.
|
||||
|
||||
This command changes the desired instance group configuration in the registry.
|
||||
|
||||
kops set does not update the cloud resources; to apply the changes use "kops update cluster".`))
|
||||
|
||||
setInstancegroupExample = templates.Examples(i18n.T(`
|
||||
# Set instance group to run image custom-ami-image
|
||||
kops set instancegroup --name k8s-cluster.example.com nodes spec.image=custom-ami-image
|
||||
`))
|
||||
)
|
||||
|
||||
// NewCmdSetInstancegroup builds a cobra command for the kops set instancegroup command.
|
||||
func NewCmdSetInstancegroup(f *util.Factory, out io.Writer) *cobra.Command {
|
||||
options := &commands.SetInstanceGroupOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "instancegroup",
|
||||
Aliases: []string{"instancegroups", "ig"},
|
||||
Short: i18n.T("Set instancegroup fields."),
|
||||
Long: setInstancegroupLong,
|
||||
Example: setInstancegroupExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.TODO()
|
||||
|
||||
for i, arg := range args {
|
||||
index := strings.Index(arg, "=")
|
||||
|
||||
if i == 0 {
|
||||
if index != -1 {
|
||||
exitWithError(fmt.Errorf("Specify name of instance group to edit"))
|
||||
}
|
||||
options.InstanceGroupName = arg
|
||||
} else {
|
||||
if index == -1 {
|
||||
exitWithError(fmt.Errorf("unrecognized parameter %q (missing '=')", arg))
|
||||
return
|
||||
}
|
||||
options.Fields = append(options.Fields, arg)
|
||||
}
|
||||
}
|
||||
|
||||
options.ClusterName = rootCommand.ClusterName()
|
||||
|
||||
if err := commands.RunSetInstancegroup(ctx, f, cmd, out, options); err != nil {
|
||||
exitWithError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
@ -16,6 +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 k8s-cluster.example.com spec.maxSize=4
|
||||
```
|
||||
|
||||
### Options
|
||||
|
|
@ -49,4 +50,5 @@ Set a configuration field.
|
|||
|
||||
* [kops](kops.md) - kOps is Kubernetes Operations.
|
||||
* [kops set cluster](kops_set_cluster.md) - Set cluster fields.
|
||||
* [kops set instancegroup](kops_set_instancegroup.md) - Set instancegroup fields.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 set instancegroup
|
||||
|
||||
Set instancegroup fields.
|
||||
|
||||
### Synopsis
|
||||
|
||||
Set an instance group field value.
|
||||
|
||||
This command changes the desired instance group configuration in the registry.
|
||||
|
||||
kops set does not update the cloud resources; to apply the changes use "kops update cluster".
|
||||
|
||||
```
|
||||
kops set instancegroup [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
# Set instance group to run image custom-ami-image
|
||||
kops set instancegroup --name k8s-cluster.example.com nodes spec.image=custom-ami-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 set](kops_set.md) - Set fields on clusters and other resources.
|
||||
|
||||
|
|
@ -6,6 +6,7 @@ go_library(
|
|||
"helpers.go",
|
||||
"helpers_readwrite.go",
|
||||
"set_cluster.go",
|
||||
"set_instancegroups.go",
|
||||
"status_discovery.go",
|
||||
"version.go",
|
||||
],
|
||||
|
|
@ -39,7 +40,10 @@ go_library(
|
|||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["set_cluster_test.go"],
|
||||
srcs = [
|
||||
"set_cluster_test.go",
|
||||
"set_instancegroups_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/kops:go_default_library",
|
||||
|
|
|
|||
|
|
@ -67,6 +67,38 @@ func UpdateCluster(ctx context.Context, clientset simple.Clientset, cluster *kop
|
|||
return nil
|
||||
}
|
||||
|
||||
// UpdateInstanceGroup writes the updated instance group to the state store after performing validation
|
||||
func UpdateInstanceGroup(ctx context.Context, clientset simple.Clientset, cluster *kops.Cluster, allInstanceGroups []*kops.InstanceGroup, instanceGroupToUpdate *kops.InstanceGroup) error {
|
||||
cloud, err := cloudup.BuildCloud(cluster)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cloudup.PerformAssignments(cluster, cloud)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error populating configuration: %v", err)
|
||||
}
|
||||
|
||||
assetBuilder := assets.NewAssetBuilder(cluster, "")
|
||||
fullCluster, err := cloudup.PopulateClusterSpec(clientset, cluster, cloud, assetBuilder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = validation.DeepValidate(fullCluster, allInstanceGroups, true, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validation was successful so commit the changed instance group.
|
||||
_, err = clientset.InstanceGroupsFor(cluster).Update(ctx, instanceGroupToUpdate, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadAllInstanceGroups reads all the instance groups for the cluster
|
||||
func ReadAllInstanceGroups(ctx context.Context, clientset simple.Clientset, cluster *kops.Cluster) ([]*kops.InstanceGroup, error) {
|
||||
list, err := clientset.InstanceGroupsFor(cluster).List(ctx, metav1.ListOptions{})
|
||||
|
|
|
|||
|
|
@ -24,6 +24,17 @@ import (
|
|||
"k8s.io/kops/upup/pkg/fi"
|
||||
)
|
||||
|
||||
func TestSetClusterBadInput(t *testing.T) {
|
||||
fields := []string{
|
||||
"bad-set-input",
|
||||
}
|
||||
|
||||
err := SetClusterFields(fields, &kops.Cluster{}, []*kops.InstanceGroup{})
|
||||
if err == nil {
|
||||
t.Errorf("expected a field parsing error, but received none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetClusterFields(t *testing.T) {
|
||||
grid := []struct {
|
||||
Fields []string
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
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 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"
|
||||
)
|
||||
|
||||
// SetInstanceGroupOptions contains the options for setting configuration on an
|
||||
// instance group.
|
||||
type SetInstanceGroupOptions struct {
|
||||
Fields []string
|
||||
ClusterName string
|
||||
InstanceGroupName string
|
||||
}
|
||||
|
||||
// RunSetInstancegroup implements the set instancegroup command logic.
|
||||
func RunSetInstancegroup(ctx context.Context, f *util.Factory, cmd *cobra.Command, out io.Writer, options *SetInstanceGroupOptions) error {
|
||||
if !featureflag.SpecOverrideFlag.Enabled() {
|
||||
return fmt.Errorf("set 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 = SetInstancegroupFields(options.Fields, instanceGroupToUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = UpdateInstanceGroup(ctx, clientset, cluster, instanceGroups, instanceGroupToUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetInstancegroupFields sets field values in the instance group.
|
||||
func SetInstancegroupFields(fields []string, instanceGroup *api.InstanceGroup) error {
|
||||
for _, field := range fields {
|
||||
kv := strings.SplitN(field, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
return fmt.Errorf("unhandled field: %q", field)
|
||||
}
|
||||
|
||||
key := kv[0]
|
||||
key = strings.TrimPrefix(key, "instancegroup.")
|
||||
|
||||
if err := reflectutils.SetString(instanceGroup, key, kv[1]); err != nil {
|
||||
return fmt.Errorf("failed to set %s=%s: %v", kv[0], kv[1], err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
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 TestSetInstanceGroupsBadInput(t *testing.T) {
|
||||
fields := []string{
|
||||
"bad-set-input",
|
||||
}
|
||||
|
||||
err := SetInstancegroupFields(fields, &kops.InstanceGroup{})
|
||||
if err == nil {
|
||||
t.Errorf("expected a field parsing error, but received none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetInstanceGroupsFields(t *testing.T) {
|
||||
grid := []struct {
|
||||
Fields []string
|
||||
Input kops.InstanceGroup
|
||||
Output kops.InstanceGroup
|
||||
}{
|
||||
{
|
||||
Fields: []string{
|
||||
"spec.image=ami-test-2",
|
||||
},
|
||||
Input: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{
|
||||
Image: "ami-test-1",
|
||||
},
|
||||
},
|
||||
Output: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{
|
||||
Image: "ami-test-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"spec.machineType=m5.large",
|
||||
},
|
||||
Output: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{
|
||||
MachineType: "m5.large",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"spec.minSize=1",
|
||||
"spec.maxSize=3",
|
||||
},
|
||||
Output: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{
|
||||
MinSize: fi.Int32(1),
|
||||
MaxSize: fi.Int32(3),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"spec.role=Master",
|
||||
},
|
||||
Output: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{
|
||||
Role: "Master",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fields: []string{
|
||||
"spec.additionalSecurityGroups=group1,group2,group3",
|
||||
},
|
||||
Output: kops.InstanceGroup{
|
||||
Spec: kops.InstanceGroupSpec{
|
||||
AdditionalSecurityGroups: []string{
|
||||
"group1",
|
||||
"group2",
|
||||
"group3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, g := range grid {
|
||||
ig := g.Input
|
||||
|
||||
err := SetInstancegroupFields(g.Fields, &ig)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error from setClusterFields %v: %v", g.Fields, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ig, g.Output) {
|
||||
t.Errorf("unexpected output from setClusterFields %v. expected=%v, actual=%v", g.Fields, g.Output, ig)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue