Skip to content

Commit d1681a4

Browse files
authored
Merge pull request #16980 from justinsb/reconcile_top_level_command
Add `kops reconcile cluster` command
2 parents 3a8a13f + ab613ff commit d1681a4

13 files changed

+437
-94
lines changed

cmd/kops/create_cluster.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -802,9 +802,10 @@ func RunCreateCluster(ctx context.Context, f *util.Factory, out io.Writer, c *Cr
802802
updateClusterOptions.Yes = c.Yes
803803
updateClusterOptions.Target = c.Target
804804
updateClusterOptions.OutDir = c.OutDir
805-
updateClusterOptions.admin = kubeconfig.DefaultKubecfgAdminLifetime
806805
updateClusterOptions.ClusterName = cluster.Name
807-
updateClusterOptions.CreateKubecfg = true
806+
807+
updateClusterOptions.CreateKubecfgOptions.Admin = kubeconfig.DefaultKubecfgAdminLifetime
808+
updateClusterOptions.CreateKubecfgOptions.CreateKubecfg = true
808809

809810
// SSHPublicKey has already been mapped
810811
updateClusterOptions.SSHPublicKey = ""

cmd/kops/export_kubeconfig.go

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"context"
2121
"fmt"
2222
"io"
23-
"time"
2423

2524
"github.com/spf13/cobra"
2625
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -58,12 +57,7 @@ type ExportKubeconfigOptions struct {
5857
ClusterName string
5958
KubeConfigPath string
6059
all bool
61-
admin time.Duration
62-
user string
63-
internal bool
64-
65-
// UseKopsAuthenticationPlugin controls whether we should use the kOps auth helper instead of a static credential
66-
UseKopsAuthenticationPlugin bool
60+
kubeconfig.CreateKubecfgOptions
6761
}
6862

6963
func NewCmdExportKubeconfig(f *util.Factory, out io.Writer) *cobra.Command {
@@ -76,7 +70,7 @@ func NewCmdExportKubeconfig(f *util.Factory, out io.Writer) *cobra.Command {
7670
Long: exportKubeconfigLong,
7771
Example: exportKubeconfigExample,
7872
Args: func(cmd *cobra.Command, args []string) error {
79-
if options.admin != 0 && options.user != "" {
73+
if options.Admin != 0 && options.User != "" {
8074
return fmt.Errorf("cannot use both --admin and --user")
8175
}
8276
if options.all {
@@ -96,11 +90,11 @@ func NewCmdExportKubeconfig(f *util.Factory, out io.Writer) *cobra.Command {
9690

9791
cmd.Flags().StringVar(&options.KubeConfigPath, "kubeconfig", options.KubeConfigPath, "Filename of the kubeconfig to create")
9892
cmd.Flags().BoolVar(&options.all, "all", options.all, "Export all clusters from the kOps state store")
99-
cmd.Flags().DurationVar(&options.admin, "admin", options.admin, "Also export a cluster admin user credential with the specified lifetime and add it to the cluster context")
93+
cmd.Flags().DurationVar(&options.Admin, "admin", options.Admin, "Also export a cluster admin user credential with the specified lifetime and add it to the cluster context")
10094
cmd.Flags().Lookup("admin").NoOptDefVal = kubeconfig.DefaultKubecfgAdminLifetime.String()
101-
cmd.Flags().StringVar(&options.user, "user", options.user, "Existing user in kubeconfig file to use")
95+
cmd.Flags().StringVar(&options.User, "user", options.User, "Existing user in kubeconfig file to use")
10296
cmd.RegisterFlagCompletionFunc("user", completeKubecfgUser)
103-
cmd.Flags().BoolVar(&options.internal, "internal", options.internal, "Use the cluster's internal DNS name")
97+
cmd.Flags().BoolVar(&options.Internal, "internal", options.Internal, "Use the cluster's internal DNS name")
10498
cmd.Flags().BoolVar(&options.UseKopsAuthenticationPlugin, "auth-plugin", options.UseKopsAuthenticationPlugin, "Use the kOps authentication plugin")
10599

106100
return cmd
@@ -150,11 +144,8 @@ func RunExportKubeconfig(ctx context.Context, f *util.Factory, out io.Writer, op
150144
keyStore,
151145
secretStore,
152146
cloud,
153-
options.admin,
154-
options.user,
155-
options.internal,
156-
f.KopsStateStore(),
157-
options.UseKopsAuthenticationPlugin)
147+
options.CreateKubecfgOptions,
148+
f.KopsStateStore())
158149
if err != nil {
159150
return err
160151
}

cmd/kops/get_assets.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,11 @@ func NewCmdGetAssets(f *util.Factory, out io.Writer, getOptions *GetOptions) *co
102102

103103
func RunGetAssets(ctx context.Context, f *util.Factory, out io.Writer, options *GetAssetsOptions) error {
104104
updateClusterResults, err := RunUpdateCluster(ctx, f, out, &UpdateClusterOptions{
105-
Target: cloudup.TargetDryRun,
106-
GetAssets: true,
107-
ClusterName: options.ClusterName,
105+
CoreUpdateClusterOptions: CoreUpdateClusterOptions{
106+
Target: cloudup.TargetDryRun,
107+
GetAssets: true,
108+
ClusterName: options.ClusterName,
109+
},
108110
})
109111
if err != nil {
110112
return err

cmd/kops/reconcile.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"io"
21+
22+
"github.com/spf13/cobra"
23+
"k8s.io/kops/cmd/kops/util"
24+
"k8s.io/kubectl/pkg/util/i18n"
25+
)
26+
27+
var reconcileShort = i18n.T("Reconcile a cluster.")
28+
29+
func NewCmdReconcile(f *util.Factory, out io.Writer) *cobra.Command {
30+
cmd := &cobra.Command{
31+
Use: "reconcile",
32+
Short: reconcileShort,
33+
}
34+
35+
// subcommands
36+
cmd.AddCommand(NewCmdReconcileCluster(f, out))
37+
38+
return cmd
39+
}

cmd/kops/reconcile_cluster.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"io"
23+
24+
"github.com/spf13/cobra"
25+
"k8s.io/kops/cmd/kops/util"
26+
"k8s.io/kops/pkg/apis/kops"
27+
"k8s.io/kops/pkg/commands/commandutils"
28+
"k8s.io/kops/upup/pkg/fi/cloudup"
29+
"k8s.io/kubectl/pkg/util/i18n"
30+
"k8s.io/kubectl/pkg/util/templates"
31+
)
32+
33+
var (
34+
reconcileClusterLong = templates.LongDesc(i18n.T(`
35+
Reconcile the cluster by updating and rolling the control plane and nodes sequentially.
36+
`))
37+
38+
reconcileClusterExample = templates.Examples(i18n.T(`
39+
# After the cluster has been edited or upgraded, update the cloud resources with:
40+
kops reconcile cluster k8s-cluster.example.com --state=s3://my-state-store --yes
41+
`))
42+
43+
reconcileClusterShort = i18n.T("Reconcile a cluster.")
44+
)
45+
46+
type ReconcileClusterOptions struct {
47+
CoreUpdateClusterOptions
48+
}
49+
50+
func NewCmdReconcileCluster(f *util.Factory, out io.Writer) *cobra.Command {
51+
options := &ReconcileClusterOptions{}
52+
options.InitDefaults()
53+
54+
cmd := &cobra.Command{
55+
Use: "cluster [CLUSTER]",
56+
Short: reconcileClusterShort,
57+
Long: reconcileClusterLong,
58+
Example: reconcileClusterExample,
59+
Args: rootCommand.clusterNameArgs(&options.ClusterName),
60+
ValidArgsFunction: commandutils.CompleteClusterName(f, true, false),
61+
RunE: func(cmd *cobra.Command, args []string) error {
62+
err := RunReconcileCluster(cmd.Context(), f, out, &options.CoreUpdateClusterOptions)
63+
return err
64+
},
65+
}
66+
67+
cmd.Flags().BoolVarP(&options.Yes, "yes", "y", options.Yes, "Create cloud resources, without --yes reconcile is in dry run mode")
68+
69+
// These flags from the update command are not obviously needed by reconcile, though we can add them if needed:
70+
//
71+
// cmd.Flags().StringVar(&options.Target, "target", options.Target, "Target - direct")
72+
// cmd.RegisterFlagCompletionFunc("target", completeUpdateClusterTarget(f, &options.CoreUpdateClusterOptions))
73+
// cmd.Flags().StringVar(&options.SSHPublicKey, "ssh-public-key", options.SSHPublicKey, "SSH public key to use (deprecated: use kops create secret instead)")
74+
// cmd.Flags().StringVar(&options.OutDir, "out", options.OutDir, "Path to write any local output")
75+
// cmd.MarkFlagDirname("out")
76+
77+
// These flags from the update command are specified to kubeconfig creation
78+
//
79+
// cmd.Flags().BoolVar(&options.CreateKubecfg, "create-kube-config", options.CreateKubecfg, "Will control automatically creating the kube config file on your local filesystem")
80+
// cmd.Flags().DurationVar(&options.Admin, "admin", options.Admin, "Also export a cluster admin user credential with the specified lifetime and add it to the cluster context")
81+
// cmd.Flags().Lookup("admin").NoOptDefVal = kubeconfig.DefaultKubecfgAdminLifetime.String()
82+
// cmd.Flags().StringVar(&options.User, "user", options.User, "Existing user in kubeconfig file to use. Implies --create-kube-config")
83+
// cmd.RegisterFlagCompletionFunc("user", completeKubecfgUser)
84+
// cmd.Flags().BoolVar(&options.Internal, "internal", options.Internal, "Use the cluster's internal DNS name. Implies --create-kube-config")
85+
86+
cmd.Flags().BoolVar(&options.AllowKopsDowngrade, "allow-kops-downgrade", options.AllowKopsDowngrade, "Allow an older version of kOps to update the cluster than last used")
87+
88+
// These flags from the update command are not obviously needed by reconcile, though we can add them if needed:
89+
//
90+
// cmd.Flags().StringSliceVar(&options.InstanceGroups, "instance-group", options.InstanceGroups, "Instance groups to update (defaults to all if not specified)")
91+
// cmd.RegisterFlagCompletionFunc("instance-group", completeInstanceGroup(f, &options.InstanceGroups, &options.InstanceGroupRoles))
92+
// cmd.Flags().StringSliceVar(&options.InstanceGroupRoles, "instance-group-roles", options.InstanceGroupRoles, "Instance group roles to update ("+strings.Join(allRoles, ",")+")")
93+
// cmd.RegisterFlagCompletionFunc("instance-group-roles", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
94+
// return sets.NewString(allRoles...).Delete(options.InstanceGroupRoles...).List(), cobra.ShellCompDirectiveNoFileComp
95+
// })
96+
// cmd.Flags().StringVar(&options.Phase, "phase", options.Phase, "Subset of tasks to run: "+strings.Join(cloudup.Phases.List(), ", "))
97+
// cmd.RegisterFlagCompletionFunc("phase", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
98+
// return cloudup.Phases.List(), cobra.ShellCompDirectiveNoFileComp
99+
// })
100+
// cmd.Flags().StringSliceVar(&options.LifecycleOverrides, "lifecycle-overrides", options.LifecycleOverrides, "comma separated list of phase overrides, example: SecurityGroups=Ignore,InternetGateway=ExistsAndWarnIfChanges")
101+
// viper.BindPFlag("lifecycle-overrides", cmd.Flags().Lookup("lifecycle-overrides"))
102+
// viper.BindEnv("lifecycle-overrides", "KOPS_LIFECYCLE_OVERRIDES")
103+
// cmd.RegisterFlagCompletionFunc("lifecycle-overrides", completeLifecycleOverrides)
104+
// cmd.Flags().BoolVar(&options.Prune, "prune", options.Prune, "Delete old revisions of cloud resources that were needed during an upgrade")
105+
// cmd.Flags().BoolVar(&options.IgnoreKubeletVersionSkew, "ignore-kubelet-version-skew", options.IgnoreKubeletVersionSkew, "Setting this to true will force updating the kubernetes version on all instance groups, regardles of which control plane version is running")
106+
107+
// cmd.Flags().BoolVar(&options.Reconcile, "reconcile", options.Reconcile, "Reconcile the cluster by rolling the control plane and nodes sequentially")
108+
109+
return cmd
110+
}
111+
112+
// ReconcileCluster updates the cluster to the desired state, including rolling updates where necessary.
113+
// To respect skew policy, it updates the control plane first, then updates the nodes.
114+
// "update" is probably now smart enough to automatically not update the control plane if it is already at the desired version,
115+
// but we do it explicitly here to be clearer / safer.
116+
func RunReconcileCluster(ctx context.Context, f *util.Factory, out io.Writer, c *CoreUpdateClusterOptions) error {
117+
if !c.Yes {
118+
return fmt.Errorf("reconcile is only supported with --yes")
119+
}
120+
if c.Target == cloudup.TargetTerraform {
121+
return fmt.Errorf("reconcile is not supported with terraform")
122+
}
123+
124+
fmt.Fprintf(out, "Updating control plane configuration\n")
125+
{
126+
opt := *c
127+
opt.InstanceGroupRoles = []string{
128+
string(kops.InstanceGroupRoleAPIServer),
129+
string(kops.InstanceGroupRoleControlPlane),
130+
}
131+
opt.Prune = false // Do not prune until after the last rolling update
132+
if _, err := RunCoreUpdateCluster(ctx, f, out, &opt); err != nil {
133+
return err
134+
}
135+
}
136+
137+
fmt.Fprintf(out, "Doing rolling-update for control plane\n")
138+
{
139+
opt := &RollingUpdateOptions{}
140+
opt.InitDefaults()
141+
opt.ClusterName = c.ClusterName
142+
opt.InstanceGroupRoles = []string{
143+
string(kops.InstanceGroupRoleAPIServer),
144+
string(kops.InstanceGroupRoleControlPlane),
145+
}
146+
opt.Yes = c.Yes
147+
if err := RunRollingUpdateCluster(ctx, f, out, opt); err != nil {
148+
return err
149+
}
150+
}
151+
152+
fmt.Fprintf(out, "Updating node configuration\n")
153+
{
154+
opt := *c
155+
// Do all roles this time, though we only expect changes to node & bastion roles
156+
opt.InstanceGroupRoles = nil
157+
opt.Prune = false // Do not prune until after the last rolling update
158+
if _, err := RunCoreUpdateCluster(ctx, f, out, &opt); err != nil {
159+
return err
160+
}
161+
}
162+
163+
fmt.Fprintf(out, "Doing rolling-update for nodes\n")
164+
{
165+
opt := &RollingUpdateOptions{}
166+
opt.InitDefaults()
167+
opt.ClusterName = c.ClusterName
168+
// Do all roles this time, though we only expect changes to node & bastion roles
169+
opt.InstanceGroupRoles = nil
170+
opt.Yes = c.Yes
171+
if err := RunRollingUpdateCluster(ctx, f, out, opt); err != nil {
172+
return err
173+
}
174+
}
175+
176+
fmt.Fprintf(out, "Pruning old resources that are no longer used\n")
177+
{
178+
opt := *c
179+
opt.InstanceGroupRoles = nil
180+
opt.Prune = true
181+
if _, err := RunCoreUpdateCluster(ctx, f, out, &opt); err != nil {
182+
return err
183+
}
184+
}
185+
186+
return nil
187+
}

cmd/kops/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ func NewCmdRoot(f *util.Factory, out io.Writer) *cobra.Command {
171171
cmd.AddCommand(NewCmdGet(f, out))
172172
cmd.AddCommand(commands.NewCmdHelpers(f, out))
173173
cmd.AddCommand(NewCmdPromote(f, out))
174+
cmd.AddCommand(NewCmdReconcile(f, out))
174175
cmd.AddCommand(NewCmdReplace(f, out))
175176
cmd.AddCommand(NewCmdRollingUpdate(f, out))
176177
cmd.AddCommand(NewCmdToolbox(f, out))

0 commit comments

Comments
 (0)