Skip to content

Commit eb4de58

Browse files
clusterctl add the upgrade plan cmd
1 parent 0a9b0fd commit eb4de58

15 files changed

+341
-33
lines changed

cmd/clusterctl/cmd/upgrade.go

+132-1
Original file line numberDiff line numberDiff line change
@@ -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 last available version within the current cluster API version.
54+
- The last available version within 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.UpgradePlan(client.UpgradePlanOptions{
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
@@ -120,6 +120,12 @@ type MoveOptions struct {
120120
Namespace string
121121
}
122122

123+
// UpgradePlanOptions carries the options supported by upgrade plan.
124+
type UpgradePlanOptions struct {
125+
// Kubeconfig file to use for accessing the management cluster. If empty, default rules for kubeconfig discovery will be used.
126+
Kubeconfig string
127+
}
128+
123129
// Client is exposes the clusterctl high-level client library.
124130
type Client interface {
125131
// GetProvidersConfig returns the list of providers configured for this instance of clusterctl.
@@ -139,6 +145,13 @@ type Client interface {
139145

140146
// Move moves all the Cluster API objects existing in a namespace (or from all the namespaces if empty) to a target management cluster.
141147
Move(options MoveOptions) error
148+
149+
// Plan returns a set of suggested Upgrade plans for the cluster, and more specifically:
150+
// - Each management group gets separated upgrade plans.
151+
// - For each management group, an upgrade plan will be generated for each ClusterAPIVersion available, e.g.
152+
// - Upgrade to the latest version in the the v1alpha2 series: ....
153+
// - Upgrade to the latest version in the the v1alpha3 series: ....
154+
UpgradePlan(options UpgradePlanOptions) ([]UpgradePlan, error)
142155
}
143156

144157
// clusterctlClient implements Client.
@@ -218,7 +231,7 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err
218231
// if there is an injected ClusterFactory, use it, otherwise use a default one.
219232
clusterClientFactory := cfg.injectClusterFactory
220233
if clusterClientFactory == nil {
221-
clusterClientFactory = defaultClusterFactory()
234+
clusterClientFactory = defaultClusterFactory(configClient)
222235
}
223236

224237
return &clusterctlClient{
@@ -229,9 +242,9 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err
229242
}
230243

231244
// defaultClusterFactory is a ClusterClientFactory func the uses the default client provided by the cluster low level library.
232-
func defaultClusterFactory() func(kubeconfig string) (cluster.Client, error) {
245+
func defaultClusterFactory(configClient config.Client) func(kubeconfig string) (cluster.Client, error) {
233246
return func(kubeconfig string) (cluster.Client, error) {
234-
return cluster.New(kubeconfig), nil
247+
return cluster.New(kubeconfig, configClient), nil
235248
}
236249
}
237250

cmd/clusterctl/pkg/client/client_test.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ func (f fakeClient) Move(options MoveOptions) error {
8686
return f.internalClient.Move(options)
8787
}
8888

89+
func (f fakeClient) UpgradePlan(options UpgradePlanOptions) ([]UpgradePlan, error) {
90+
return f.internalClient.UpgradePlan(options)
91+
}
92+
8993
// newFakeClient returns a clusterctl client that allows to execute tests on a set of fake config, fake repositories and fake clusters.
9094
// you can use WithCluster and WithRepository to prepare for the test case.
9195
func newFakeClient(configClient config.Client) *fakeClient {
@@ -140,7 +144,9 @@ func (f *fakeClient) WithRepository(repositoryClient repository.Client) *fakeCli
140144
func newFakeCluster(kubeconfig string) *fakeClusterClient {
141145
fakeProxy := test.NewFakeProxy()
142146

143-
client := cluster.New("", cluster.InjectProxy(fakeProxy))
147+
configClient := newFakeConfig()
148+
149+
client := cluster.New("", configClient, cluster.InjectProxy(fakeProxy))
144150

145151
return &fakeClusterClient{
146152
kubeconfig: kubeconfig,
@@ -199,6 +205,10 @@ func (f *fakeClusterClient) ObjectMover() cluster.ObjectMover {
199205
return f.internalclient.ObjectMover()
200206
}
201207

208+
func (f *fakeClusterClient) ProviderUpgrader() cluster.ProviderUpgrader {
209+
return f.internalclient.ProviderUpgrader()
210+
}
211+
202212
func (f *fakeClusterClient) WithObjs(objs ...runtime.Object) *fakeClusterClient {
203213
f.fakeProxy.WithObjs(objs...)
204214
return f

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

+12-3
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,16 @@ type Client interface {
6666
// ObjectMover returns an ObjectMover that implements support for moving Cluster API objects (e.g. clusters, AWS clusters, machines, etc.).
6767
// from one management cluster to another management cluster.
6868
ObjectMover() ObjectMover
69+
70+
// ProviderUpgrader returns an ObjectMover that implements support upgrading ClusterAPI providers.
71+
ProviderUpgrader() ProviderUpgrader
6972
}
7073

7174
// clusterClient implements Client.
7275
type clusterClient struct {
7376
kubeconfig string
7477
proxy Proxy
78+
configClient config.Client
7579
repositoryClientFactory RepositoryClientFactory
7680
}
7781

@@ -114,6 +118,10 @@ func (c *clusterClient) ObjectMover() ObjectMover {
114118
return newObjectMover(c.proxy, log)
115119
}
116120

121+
func (c *clusterClient) ProviderUpgrader() ProviderUpgrader {
122+
return newProviderUpgrader(c.configClient, c.repositoryClientFactory, c.ProviderInventory())
123+
}
124+
117125
// NewOptions carries the options supported by New
118126
type NewOptions struct {
119127
injectProxy Proxy
@@ -139,11 +147,11 @@ func InjectRepositoryFactory(factory RepositoryClientFactory) Option {
139147
}
140148

141149
// New returns a cluster.Client.
142-
func New(kubeconfig string, options ...Option) Client {
143-
return newClusterClient(kubeconfig, options...)
150+
func New(kubeconfig string, configClient config.Client, options ...Option) Client {
151+
return newClusterClient(kubeconfig, configClient, options...)
144152
}
145153

146-
func newClusterClient(kubeconfig string, options ...Option) *clusterClient {
154+
func newClusterClient(kubeconfig string, configClient config.Client, options ...Option) *clusterClient {
147155
cfg := &NewOptions{}
148156
for _, o := range options {
149157
o(cfg)
@@ -162,6 +170,7 @@ func newClusterClient(kubeconfig string, options ...Option) *clusterClient {
162170
}
163171

164172
return &clusterClient{
173+
configClient: configClient,
165174
kubeconfig: kubeconfig,
166175
proxy: proxy,
167176
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
},

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

+25-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package cluster
1818

1919
import (
20+
"fmt"
21+
2022
"github.com/pkg/errors"
2123
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
2224
"sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client/config"
@@ -26,7 +28,7 @@ import (
2628
type ProviderUpgrader interface {
2729
// Plan returns a set of suggested Upgrade plans for the cluster, and more specifically:
2830
// - Each management group gets separated upgrade plans.
29-
// - For each management group, an upgrade plann will be generated for each ClusterAPIVersion available, e.g.
31+
// - For each management group, an upgrade plan will be generated for each ClusterAPIVersion available, e.g.
3032
// - Upgrade to the latest version in the the v1alpha2 series: ....
3133
// - Upgrade to the latest version in the the v1alpha3 series: ....
3234
Plan() ([]UpgradePlan, error)
@@ -35,15 +37,27 @@ type ProviderUpgrader interface {
3537
// UpgradePlan defines a list of possible upgrade targets for a management group.
3638
type UpgradePlan struct {
3739
ClusterAPIVersion string
40+
CoreProvider clusterctlv1.Provider
3841
Providers []UpgradeItem
3942
}
4043

44+
// UgradeRef returns a string identifying the upgrade plan; this string is derived by the core provider which is
45+
// unique for each management group.
46+
func (u *UpgradePlan) UgradeRef() string {
47+
return fmt.Sprintf("%s/%s", u.CoreProvider.Namespace, u.CoreProvider.Name)
48+
}
49+
4150
// UpgradeItem defines a possible upgrade target for a provider in the management group.
4251
type UpgradeItem struct {
43-
Provider clusterctlv1.Provider
52+
clusterctlv1.Provider
4453
NextVersion string
4554
}
4655

56+
// UgradeRef returns a string identifying the upgrade item; this string is derived by the provider.
57+
func (u *UpgradeItem) UgradeRef() string {
58+
return fmt.Sprintf("%s/%s", u.Namespace, u.Name)
59+
}
60+
4761
type providerUpgrader struct {
4862
configClient config.Client
4963
repositoryClientFactory RepositoryClientFactory
@@ -104,10 +118,19 @@ func (u *providerUpgrader) Plan() ([]UpgradePlan, error) {
104118

105119
ret = append(ret, UpgradePlan{
106120
ClusterAPIVersion: apiVersion,
121+
CoreProvider: managementGroup.CoreProvider,
107122
Providers: upgradeItems,
108123
})
109124
}
110125
}
111126

112127
return ret, nil
113128
}
129+
130+
func newProviderUpgrader(configClient config.Client, repositoryClientFactory RepositoryClientFactory, providerInventory InventoryClient) *providerUpgrader {
131+
return &providerUpgrader{
132+
configClient: configClient,
133+
repositoryClientFactory: repositoryClientFactory,
134+
providerInventory: providerInventory,
135+
}
136+
}

0 commit comments

Comments
 (0)