Skip to content

Commit 49eea4a

Browse files
clusterctl add the upgrade plan cmd
1 parent 486353a commit 49eea4a

15 files changed

+356
-38
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\tTARGET 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.TargetVersion))
109+
if upgradeItem.TargetVersion != "" {
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

+14-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ type MoveOptions struct {
6161
Namespace string
6262
}
6363

64+
// UpgradePlanOptions carries the options supported by upgrade plan.
65+
type UpgradePlanOptions struct {
66+
// Kubeconfig file to use for accessing the management cluster. If empty, default rules for kubeconfig discovery will be used.
67+
Kubeconfig string
68+
}
69+
6470
// Client is exposes the clusterctl high-level client library.
6571
type Client interface {
6672
// GetProvidersConfig returns the list of providers configured for this instance of clusterctl.
@@ -80,6 +86,13 @@ type Client interface {
8086

8187
// Move moves all the Cluster API objects existing in a namespace (or from all the namespaces if empty) to a target management cluster.
8288
Move(options MoveOptions) error
89+
90+
// Plan returns a set of suggested Upgrade plans for the cluster, and more specifically:
91+
// - Each management group gets separated upgrade plans.
92+
// - For each management group, an upgrade plan will be generated for each ClusterAPIVersion available, e.g.
93+
// - Upgrade to the latest version in the the v1alpha2 series: ....
94+
// - Upgrade to the latest version in the the v1alpha3 series: ....
95+
UpgradePlan(options UpgradePlanOptions) ([]UpgradePlan, error)
8396
}
8497

8598
// clusterctlClient implements Client.
@@ -172,7 +185,7 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err
172185
// defaultClusterFactory is a ClusterClientFactory func the uses the default client provided by the cluster low level library.
173186
func defaultClusterFactory() func(kubeconfig string) (cluster.Client, error) {
174187
return func(kubeconfig string) (cluster.Client, error) {
175-
return cluster.New(kubeconfig, cluster.Options{}), nil
188+
return cluster.New(kubeconfig, cluster.Options{})
176189
}
177190
}
178191

cmd/clusterctl/pkg/client/client_test.go

+12-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 {
@@ -144,7 +148,10 @@ func newFakeCluster(kubeconfig string) *fakeClusterClient {
144148
InjectProxy: fakeProxy,
145149
}
146150

147-
client := cluster.New("", options)
151+
client, err := cluster.New("", options)
152+
if err != nil {
153+
panic(err)
154+
}
148155

149156
return &fakeClusterClient{
150157
kubeconfig: kubeconfig,
@@ -203,6 +210,10 @@ func (f *fakeClusterClient) ObjectMover() cluster.ObjectMover {
203210
return f.internalclient.ObjectMover()
204211
}
205212

213+
func (f *fakeClusterClient) ProviderUpgrader() cluster.ProviderUpgrader {
214+
return f.internalclient.ProviderUpgrader()
215+
}
216+
206217
func (f *fakeClusterClient) WithObjs(objs ...runtime.Object) *fakeClusterClient {
207218
f.fakeProxy.WithObjs(objs...)
208219
return f

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

+23-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
machineWaiter MachineWaiter
7781
}
@@ -113,14 +117,28 @@ func (c *clusterClient) ObjectMover() ObjectMover {
113117
return newObjectMover(c.proxy, c.machineWaiter, log)
114118
}
115119

120+
func (c *clusterClient) ProviderUpgrader() ProviderUpgrader {
121+
return newProviderUpgrader(c.configClient, c.repositoryClientFactory, c.ProviderInventory()) //TODO: add logger after https://github.com/kubernetes-sigs/cluster-api/pull/2151 merge
122+
}
123+
116124
// New returns a cluster.Client.
117-
func New(kubeconfig string, options Options) Client {
125+
func New(kubeconfig string, options Options) (Client, error) {
118126
return newClusterClient(kubeconfig, options)
119127
}
120128

121129
type RepositoryClientFactory func(provider config.Provider, configVariablesClient config.VariablesClient, options repository.Options) (repository.Client, error)
122130

123-
func newClusterClient(kubeconfig string, options Options) *clusterClient {
131+
func newClusterClient(kubeconfig string, options Options) (*clusterClient, error) {
132+
// if there is an injected proxy, use it, otherwise use the default one
133+
configClient := options.InjectConfigClient //TODO: refactor after https://github.com/kubernetes-sigs/cluster-api/pull/2151 merge
134+
if configClient == nil {
135+
var err error
136+
configClient, err = config.New("")
137+
if err != nil {
138+
return nil, err
139+
}
140+
}
141+
124142
// if there is an injected proxy, use it, otherwise use the default one
125143
proxy := options.InjectProxy
126144
if proxy == nil {
@@ -140,16 +158,18 @@ func newClusterClient(kubeconfig string, options Options) *clusterClient {
140158
}
141159

142160
return &clusterClient{
161+
configClient: configClient,
143162
kubeconfig: kubeconfig,
144163
proxy: proxy,
145164
repositoryClientFactory: repositoryClientFactory,
146165
machineWaiter: machineWaiter,
147-
}
166+
}, nil
148167
}
149168

150169
// Options allow to set ConfigClient options
151170
type Options struct {
152171
InjectProxy Proxy
172+
InjectConfigClient config.Client
153173
InjectRepositoryClientFactory RepositoryClientFactory
154174
InjectMachineWaiter MachineWaiter
155175
}

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)