Skip to content

Commit 936d92b

Browse files
clusterctl add the upgrade plan cmd
1 parent f208101 commit 936d92b

15 files changed

+341
-35
lines changed

cmd/clusterctl/cmd/upgrade.go

+133-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2019 The Kubernetes Authors.
2+
Copyright 2020 The Kubernetes Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -16,4 +16,135 @@ limitations under the License.
1616

1717
package cmd
1818

19-
// TODO: upgrade command
19+
import (
20+
"fmt"
21+
"os"
22+
"sort"
23+
"text/tabwriter"
24+
25+
"github.com/spf13/cobra"
26+
"sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client"
27+
)
28+
29+
var upgradeCmd = &cobra.Command{
30+
Use: "upgrade",
31+
Short: "Upgrades Cluster API providers in a management cluster",
32+
RunE: func(cmd *cobra.Command, args []string) error {
33+
return cmd.Help()
34+
},
35+
}
36+
37+
type upgradePlanOptions struct {
38+
kubeconfig string
39+
}
40+
41+
var up = &upgradePlanOptions{}
42+
43+
var upgradePlanCmd = &cobra.Command{
44+
Use: "plan",
45+
Short: "Provide a list of recommended target versions for upgrading Cluster API providers in a management cluster",
46+
Long: LongDesc(`
47+
The upgrade plan command provides a list of recommended target versions for upgrading Cluster API providers in a management cluster.
48+
49+
The providers are grouped into management groups, each one defining a set of providers that should be supporting the same cluster API version
50+
in order to guarantee the proper functioning of the management cluster.
51+
52+
Then, for each provider in a management group, the following upgrade options are provided:
53+
- The latest patch release for the current Cluster API version.
54+
- The latest patch release for the next Cluster API version, if available.`),
55+
56+
Example: Examples(`
57+
# Gets the recommended target versions for upgrading Cluster API providers.
58+
clusterctl upgrade plan`),
59+
60+
RunE: func(cmd *cobra.Command, args []string) error {
61+
return runUpgradePlan()
62+
},
63+
}
64+
65+
func init() {
66+
upgradePlanCmd.Flags().StringVarP(&up.kubeconfig, "kubeconfig", "", "", "Path to the kubeconfig file to use for accessing the management cluster. If empty, default rules for kubeconfig discovery will be used")
67+
68+
upgradeCmd.AddCommand(upgradePlanCmd)
69+
70+
RootCmd.AddCommand(upgradeCmd)
71+
}
72+
73+
func runUpgradePlan() error {
74+
c, err := client.New(cfgFile)
75+
if err != nil {
76+
return err
77+
}
78+
79+
//TODO: switch to klog as soon as https://github.com/kubernetes-sigs/cluster-api/pull/2150 merge
80+
fmt.Println("Checking new release availability...")
81+
upgradePlans, err := c.PlanUpgrade(client.PlanUpgradeOptions{
82+
Kubeconfig: up.kubeconfig,
83+
})
84+
if err != nil {
85+
return err
86+
}
87+
88+
// ensure upgrade plans are sorted consistently (by CoreProvider.Namespace, ClusterAPIVersion).
89+
sortUpgradePlans(upgradePlans)
90+
91+
if len(upgradePlans) == 0 {
92+
fmt.Println("There are no management groups in the cluster. Please use clusterctl init to initialize a Cluster API management cluster.")
93+
return nil
94+
}
95+
96+
for _, plan := range upgradePlans {
97+
// ensure provider are sorted consistently (by Type, Name, Namespace).
98+
sortUpgradeItems(plan)
99+
100+
upgradeAvailable := false
101+
102+
fmt.Println("")
103+
fmt.Printf("Management group: %s/%s, latest release available for the %s Cluster API version:\n", plan.CoreProvider.Namespace, plan.CoreProvider.Name, plan.ClusterAPIVersion)
104+
fmt.Println("")
105+
w := tabwriter.NewWriter(os.Stdout, 10, 4, 3, ' ', 0)
106+
fmt.Fprintln(w, "NAME\tNAMESPACE\tTYPE\tCURRENT VERSION\tNEXT VERSION")
107+
for _, upgradeItem := range plan.Providers {
108+
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", upgradeItem.Provider.Name, upgradeItem.Provider.Namespace, upgradeItem.Provider.Type, upgradeItem.Provider.Version, prettifyTargetVersion(upgradeItem.NextVersion))
109+
if upgradeItem.NextVersion != "" {
110+
upgradeAvailable = true
111+
}
112+
}
113+
w.Flush()
114+
fmt.Println("")
115+
116+
if upgradeAvailable {
117+
fmt.Println("You can now apply the upgrade by executing the following command:")
118+
fmt.Println("")
119+
fmt.Println(fmt.Sprintf(" upgrade apply --management-group %s/%s --cluster-api-version %s", plan.CoreProvider.Namespace, plan.CoreProvider.Name, plan.ClusterAPIVersion))
120+
} else {
121+
fmt.Println("You are already up to date!")
122+
}
123+
fmt.Println("")
124+
125+
}
126+
127+
return nil
128+
}
129+
130+
func sortUpgradeItems(plan client.UpgradePlan) {
131+
sort.Slice(plan.Providers, func(i, j int) bool {
132+
return plan.Providers[i].Provider.Type < plan.Providers[j].Provider.Type ||
133+
(plan.Providers[i].Provider.Type == plan.Providers[j].Provider.Type && plan.Providers[i].Provider.Name < plan.Providers[j].Provider.Name) ||
134+
(plan.Providers[i].Provider.Type == plan.Providers[j].Provider.Type && plan.Providers[i].Provider.Name == plan.Providers[j].Provider.Name && plan.Providers[i].Provider.Namespace < plan.Providers[j].Provider.Namespace)
135+
})
136+
}
137+
138+
func sortUpgradePlans(upgradePlans []client.UpgradePlan) {
139+
sort.Slice(upgradePlans, func(i, j int) bool {
140+
return upgradePlans[i].CoreProvider.Namespace < upgradePlans[j].CoreProvider.Namespace ||
141+
(upgradePlans[i].CoreProvider.Namespace == upgradePlans[j].CoreProvider.Namespace && upgradePlans[i].ClusterAPIVersion < upgradePlans[j].ClusterAPIVersion)
142+
})
143+
}
144+
145+
func prettifyTargetVersion(version string) string {
146+
if version == "" {
147+
return "Already up to date"
148+
}
149+
return version
150+
}

cmd/clusterctl/pkg/client/alias.go

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package client
1818

1919
import (
20+
"sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client/cluster"
2021
"sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client/config"
2122
"sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client/repository"
2223
)
@@ -32,3 +33,6 @@ type Components repository.Components
3233

3334
// Template wraps a YAML file that defines the cluster objects (Cluster, Machines etc.).
3435
type Template repository.Template
36+
37+
// Template wraps a YAML file that defines the cluster objects (Cluster, Machines etc.).
38+
type UpgradePlan cluster.UpgradePlan

cmd/clusterctl/pkg/client/client.go

+16-3
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ type MoveOptions struct {
117117
Namespace string
118118
}
119119

120+
// PlanUpgradeOptions carries the options supported by upgrade plan.
121+
type PlanUpgradeOptions struct {
122+
// Kubeconfig file to use for accessing the management cluster. If empty, default rules for kubeconfig discovery will be used.
123+
Kubeconfig string
124+
}
125+
120126
// Client is exposes the clusterctl high-level client library.
121127
type Client interface {
122128
// GetProvidersConfig returns the list of providers configured for this instance of clusterctl.
@@ -136,6 +142,13 @@ type Client interface {
136142

137143
// Move moves all the Cluster API objects existing in a namespace (or from all the namespaces if empty) to a target management cluster.
138144
Move(options MoveOptions) error
145+
146+
// PlanUpgrade returns a set of suggested Upgrade plans for the cluster, and more specifically:
147+
// - Each management group gets separated upgrade plans.
148+
// - For each management group, an upgrade plan is generated for each ClusterAPIVersion available, e.g.
149+
// - Upgrade to the latest version in the the v1alpha2 series: ....
150+
// - Upgrade to the latest version in the the v1alpha3 series: ....
151+
PlanUpgrade(options PlanUpgradeOptions) ([]UpgradePlan, error)
139152
}
140153

141154
// clusterctlClient implements Client.
@@ -215,7 +228,7 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err
215228
// if there is an injected ClusterFactory, use it, otherwise use a default one.
216229
clusterClientFactory := cfg.injectClusterFactory
217230
if clusterClientFactory == nil {
218-
clusterClientFactory = defaultClusterFactory()
231+
clusterClientFactory = defaultClusterFactory(configClient)
219232
}
220233

221234
return &clusterctlClient{
@@ -226,9 +239,9 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err
226239
}
227240

228241
// defaultClusterFactory is a ClusterClientFactory func the uses the default client provided by the cluster low level library.
229-
func defaultClusterFactory() func(kubeconfig string) (cluster.Client, error) {
242+
func defaultClusterFactory(configClient config.Client) func(kubeconfig string) (cluster.Client, error) {
230243
return func(kubeconfig string) (cluster.Client, error) {
231-
return cluster.New(kubeconfig), nil
244+
return cluster.New(kubeconfig, configClient), nil
232245
}
233246
}
234247

cmd/clusterctl/pkg/client/client_test.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ func (f fakeClient) Move(options MoveOptions) error {
8888
return f.internalClient.Move(options)
8989
}
9090

91+
func (f fakeClient) PlanUpgrade(options PlanUpgradeOptions) ([]UpgradePlan, error) {
92+
return f.internalClient.PlanUpgrade(options)
93+
}
94+
9195
// newFakeClient returns a clusterctl client that allows to execute tests on a set of fake config, fake repositories and fake clusters.
9296
// you can use WithCluster and WithRepository to prepare for the test case.
9397
func newFakeClient(configClient config.Client) *fakeClient {
@@ -140,13 +144,13 @@ func (f *fakeClient) WithRepository(repositoryClient repository.Client) *fakeCli
140144
// internally uses a FakeProxy (based on the controller-runtime FakeClient).
141145
// You can use WithObjs to pre-load a set of runtime objects in the cluster.
142146
func newFakeCluster(kubeconfig string) *fakeClusterClient {
147+
configClient := newFakeConfig()
143148
fakeProxy := test.NewFakeProxy()
144-
145149
pollImmediateWaiter := func(interval, timeout time.Duration, condition wait.ConditionFunc) error {
146150
return nil
147151
}
148152

149-
client := cluster.New("", cluster.InjectProxy(fakeProxy), cluster.InjectPollImmediateWaiter(pollImmediateWaiter))
153+
client := cluster.New("", configClient, cluster.InjectProxy(fakeProxy), cluster.InjectPollImmediateWaiter(pollImmediateWaiter))
150154

151155
return &fakeClusterClient{
152156
kubeconfig: kubeconfig,
@@ -205,6 +209,10 @@ func (f *fakeClusterClient) ObjectMover() cluster.ObjectMover {
205209
return f.internalclient.ObjectMover()
206210
}
207211

212+
func (f *fakeClusterClient) ProviderUpgrader() cluster.ProviderUpgrader {
213+
return f.internalclient.ProviderUpgrader()
214+
}
215+
208216
func (f *fakeClusterClient) WithObjs(objs ...runtime.Object) *fakeClusterClient {
209217
f.fakeProxy.WithObjs(objs...)
210218
return f

cmd/clusterctl/pkg/client/cluster/client.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ type Client interface {
6868
// ObjectMover returns an ObjectMover that implements support for moving Cluster API objects (e.g. clusters, AWS clusters, machines, etc.).
6969
// from one management cluster to another management cluster.
7070
ObjectMover() ObjectMover
71+
72+
// ProviderUpgrader returns a ProviderUpgrader that supports upgrading Cluster API providers.
73+
ProviderUpgrader() ProviderUpgrader
7174
}
7275

7376
// PollImmediateWaiter tries a condition func until it returns true, an error, or the timeout is reached.
@@ -77,6 +80,7 @@ type PollImmediateWaiter func(interval, timeout time.Duration, condition wait.Co
7780
type clusterClient struct {
7881
kubeconfig string
7982
proxy Proxy
83+
configClient config.Client
8084
repositoryClientFactory RepositoryClientFactory
8185
pollImmediateWaiter PollImmediateWaiter
8286
}
@@ -120,6 +124,10 @@ func (c *clusterClient) ObjectMover() ObjectMover {
120124
return newObjectMover(c.proxy, log)
121125
}
122126

127+
func (c *clusterClient) ProviderUpgrader() ProviderUpgrader {
128+
return newProviderUpgrader(c.configClient, c.repositoryClientFactory, c.ProviderInventory())
129+
}
130+
123131
// NewOptions carries the options supported by New
124132
type NewOptions struct {
125133
injectProxy Proxy
@@ -153,11 +161,11 @@ func InjectPollImmediateWaiter(pollImmediateWaiter PollImmediateWaiter) Option {
153161
}
154162

155163
// New returns a cluster.Client.
156-
func New(kubeconfig string, options ...Option) Client {
157-
return newClusterClient(kubeconfig, options...)
164+
func New(kubeconfig string, configClient config.Client, options ...Option) Client {
165+
return newClusterClient(kubeconfig, configClient, options...)
158166
}
159167

160-
func newClusterClient(kubeconfig string, options ...Option) *clusterClient {
168+
func newClusterClient(kubeconfig string, configClient config.Client, options ...Option) *clusterClient {
161169
cfg := &NewOptions{}
162170
for _, o := range options {
163171
o(cfg)
@@ -182,6 +190,7 @@ func newClusterClient(kubeconfig string, options ...Option) *clusterClient {
182190
}
183191

184192
return &clusterClient{
193+
configClient: configClient,
185194
kubeconfig: kubeconfig,
186195
proxy: proxy,
187196
repositoryClientFactory: repositoryClientFactory,

cmd/clusterctl/pkg/client/cluster/components_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232

3333
func Test_providerComponents_Delete(t *testing.T) {
3434
labels := map[string]string{
35-
clusterv1.ProviderLabelName: "aws",
35+
clusterv1.ProviderLabelName: "infra",
3636
}
3737

3838
crd := unstructured.Unstructured{}
@@ -107,7 +107,7 @@ func Test_providerComponents_Delete(t *testing.T) {
107107
{
108108
name: "Delete provider while preserving Namespace and CRDs",
109109
args: args{
110-
provider: clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "aws", Namespace: "ns1"}},
110+
provider: clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "infra", Namespace: "ns1"}},
111111
forceDeleteNamespace: false,
112112
forceDeleteCRD: false,
113113
},
@@ -123,7 +123,7 @@ func Test_providerComponents_Delete(t *testing.T) {
123123
{
124124
name: "Delete provider and provider namespace, while preserving CRDs",
125125
args: args{
126-
provider: clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "aws", Namespace: "ns1"}},
126+
provider: clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "infra", Namespace: "ns1"}},
127127
forceDeleteNamespace: true,
128128
forceDeleteCRD: false,
129129
},
@@ -140,7 +140,7 @@ func Test_providerComponents_Delete(t *testing.T) {
140140
{
141141
name: "Delete provider and provider CRDs, while preserving the provider namespace",
142142
args: args{
143-
provider: clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "aws", Namespace: "ns1"}},
143+
provider: clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "infra", Namespace: "ns1"}},
144144
forceDeleteNamespace: false,
145145
forceDeleteCRD: true,
146146
},
@@ -157,7 +157,7 @@ func Test_providerComponents_Delete(t *testing.T) {
157157
{
158158
name: "Delete provider, provider namespace and provider CRDs",
159159
args: args{
160-
provider: clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "aws", Namespace: "ns1"}},
160+
provider: clusterctlv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "infra", Namespace: "ns1"}},
161161
forceDeleteNamespace: true,
162162
forceDeleteCRD: true,
163163
},

0 commit comments

Comments
 (0)