Skip to content

Commit

Permalink
Merge pull request #318 from justinsb/upgrade_for_upgrade
Browse files Browse the repository at this point in the history
Rename old upgrade command; make new upgrade intuitive
  • Loading branch information
justinsb authored Aug 16, 2016
2 parents 230a39a + 5d8c170 commit 9320f6c
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 49 deletions.
3 changes: 1 addition & 2 deletions cmd/kops/rollingupdate_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"os"
"strconv"

"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kops/upup/pkg/fi/cloudup"
"k8s.io/kops/upup/pkg/kutil"
Expand Down Expand Up @@ -34,7 +33,7 @@ func init() {
cmd.Run = func(cmd *cobra.Command, args []string) {
err := rollingupdateCluster.Run()
if err != nil {
glog.Exitf("%v", err)
exitWithError(err)
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions cmd/kops/toolbox.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

import (
"github.com/spf13/cobra"
)

// toolboxCmd represents the toolbox command
var toolboxCmd = &cobra.Command{
Use: "toolbox",
Short: "Misc infrequently used commands",
}

func init() {
rootCommand.AddCommand(toolboxCmd)
}
100 changes: 100 additions & 0 deletions cmd/kops/toolbox_convert_imported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"fmt"

"github.com/spf13/cobra"
"k8s.io/kops/upup/pkg/api"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/kutil"
)

type ConvertImportedCmd struct {
NewClusterName string
}

var convertImported ConvertImportedCmd

func init() {
cmd := &cobra.Command{
Use: "convert-imported",
Short: "Convert an imported cluster into a kops cluster",
Run: func(cmd *cobra.Command, args []string) {
err := convertImported.Run()
if err != nil {
exitWithError(err)
}
},
}

toolboxCmd.AddCommand(cmd)

cmd.Flags().StringVar(&convertImported.NewClusterName, "newname", "", "new cluster name")
}

func (c *ConvertImportedCmd) Run() error {
clusterRegistry, cluster, err := rootCommand.Cluster()
if err != nil {
return err
}

instanceGroupRegistry, err := rootCommand.InstanceGroupRegistry()
if err != nil {
return err
}

instanceGroups, err := instanceGroupRegistry.ReadAll()

if cluster.Annotations[api.AnnotationNameManagement] != api.AnnotationValueManagementImported {
return fmt.Errorf("cluster %q does not appear to be a cluster imported using kops import", cluster.Name)
}

if c.NewClusterName == "" {
return fmt.Errorf("--newname is required for converting an imported cluster")
}

oldClusterName := cluster.Name
if oldClusterName == "" {
return fmt.Errorf("(Old) ClusterName must be set in configuration")
}

// TODO: Switch to cloudup.BuildCloud
if len(cluster.Spec.Zones) == 0 {
return fmt.Errorf("Configuration must include Zones")
}

region := ""
for _, zone := range cluster.Spec.Zones {
if len(zone.Name) <= 2 {
return fmt.Errorf("Invalid AWS zone: %q", zone.Name)
}

zoneRegion := zone.Name[:len(zone.Name)-1]
if region != "" && zoneRegion != region {
return fmt.Errorf("Clusters cannot span multiple regions")
}

region = zoneRegion
}

tags := map[string]string{"KubernetesCluster": oldClusterName}
cloud, err := awsup.NewAWSCloud(region, tags)
if err != nil {
return fmt.Errorf("error initializing AWS client: %v", err)
}

d := &kutil.ConvertKubeupCluster{}
d.NewClusterName = c.NewClusterName
d.OldClusterName = oldClusterName
d.Cloud = cloud
d.ClusterConfig = cluster
d.InstanceGroups = instanceGroups
d.ClusterRegistry = clusterRegistry

err = d.Upgrade()
if err != nil {
return err
}

return nil
}
131 changes: 93 additions & 38 deletions cmd/kops/upgrade_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package main
import (
"fmt"

"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/kutil"
"k8s.io/kops/upup/pkg/api"
"k8s.io/kops/upup/pkg/fi/cloudup"
"os"
)

type UpgradeClusterCmd struct {
Yes bool

NewClusterName string
}

Expand All @@ -23,21 +25,26 @@ func init() {
Run: func(cmd *cobra.Command, args []string) {
err := upgradeCluster.Run()
if err != nil {
glog.Exitf("%v", err)
exitWithError(err)
}
},
}

cmd.Flags().BoolVar(&upgradeCluster.Yes, "yes", false, "Apply update")

upgradeCmd.AddCommand(cmd)
}

type upgradeAction struct {
Item string
Property string
Old string
New string

cmd.Flags().StringVar(&upgradeCluster.NewClusterName, "newname", "", "new cluster name")
apply func()
}

func (c *UpgradeClusterCmd) Run() error {
if c.NewClusterName == "" {
return fmt.Errorf("--newname is required")
}

clusterRegistry, cluster, err := rootCommand.Cluster()
if err != nil {
return err
Expand All @@ -50,46 +57,94 @@ func (c *UpgradeClusterCmd) Run() error {

instanceGroups, err := instanceGroupRegistry.ReadAll()

oldClusterName := cluster.Name
if oldClusterName == "" {
return fmt.Errorf("(Old) ClusterName must be set in configuration")
if cluster.Annotations[api.AnnotationNameManagement] == api.AnnotationValueManagementImported {
return fmt.Errorf("upgrade is not for use with imported clusters (did you mean `kops toolbox convert-imported`?)")
}

latestKubernetesVersion, err := api.FindLatestKubernetesVersion()
if err != nil {
return err
}

var actions []*upgradeAction
if cluster.Spec.KubernetesVersion != latestKubernetesVersion {
actions = append(actions, &upgradeAction{
Item: "Cluster",
Property: "KubernetesVersion",
Old: cluster.Spec.KubernetesVersion,
New: latestKubernetesVersion,
apply: func() {
cluster.Spec.KubernetesVersion = latestKubernetesVersion
},
})
}

if len(actions) == 0 {
// TODO: Allow --force option to force even if not needed?
fmt.Printf("\nNo upgrade required\n")
return nil
}

if len(cluster.Spec.Zones) == 0 {
return fmt.Errorf("Configuration must include Zones")
{
t := &Table{}
t.AddColumn("ITEM", func(a *upgradeAction) string {
return a.Item
})
t.AddColumn("PROPERTY", func(a *upgradeAction) string {
return a.Property
})
t.AddColumn("OLD", func(a *upgradeAction) string {
return a.Old
})
t.AddColumn("NEW", func(a *upgradeAction) string {
return a.New
})

err := t.Render(actions, os.Stdout, "ITEM", "PROPERTY", "OLD", "NEW")
if err != nil {
return err
}
}

region := ""
for _, zone := range cluster.Spec.Zones {
if len(zone.Name) <= 2 {
return fmt.Errorf("Invalid AWS zone: %q", zone.Name)
if !c.Yes {
fmt.Printf("\nMust specify --yes to perform upgrade\n")
return nil
} else {
for _, action := range actions {
action.apply()
}

zoneRegion := zone.Name[:len(zone.Name)-1]
if region != "" && zoneRegion != region {
return fmt.Errorf("Clusters cannot span multiple regions")
// TODO: DRY this chunk
err = cluster.PerformAssignments()
if err != nil {
return fmt.Errorf("error populating configuration: %v", err)
}

region = zoneRegion
}
fullCluster, err := cloudup.PopulateClusterSpec(cluster, clusterRegistry)
if err != nil {
return err
}

tags := map[string]string{"KubernetesCluster": oldClusterName}
cloud, err := awsup.NewAWSCloud(region, tags)
if err != nil {
return fmt.Errorf("error initializing AWS client: %v", err)
}
err = api.DeepValidate(fullCluster, instanceGroups, true)
if err != nil {
return err
}

d := &kutil.UpgradeCluster{}
d.NewClusterName = c.NewClusterName
d.OldClusterName = oldClusterName
d.Cloud = cloud
d.ClusterConfig = cluster
d.InstanceGroups = instanceGroups
d.ClusterRegistry = clusterRegistry
// Note we perform as much validation as we can, before writing a bad config
err = clusterRegistry.Update(cluster)
if err != nil {
return err
}

err = d.Upgrade()
if err != nil {
return err
err = clusterRegistry.WriteCompletedConfig(fullCluster)
if err != nil {
return fmt.Errorf("error writing completed cluster spec: %v", err)
}

fmt.Printf("\nUpdates applied to configuration.\n")

// TODO: automate this step
fmt.Printf("You can now apply these changes, using `kops update cluster %s`\n", cluster.Name)
}

return nil
Expand Down
4 changes: 2 additions & 2 deletions docs/upgrade_from_k8s_12.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Now have a look at the cluster configuration, to make sure it looks right. If i
open an issue.

```
kops edit cluster ${OLD_NAME}
kops get cluster ${OLD_NAME} -oyaml
````
## Move resources to a new cluster
Expand All @@ -62,7 +62,7 @@ The upgrade procedure forces you to choose a new cluster name (e.g. `k8s.mydomai
```
export NEW_NAME=k8s.mydomain.com
kops upgrade cluster --newname ${NEW_NAME} --name ${OLD_NAME}
kops toolbox convert-imported --newname ${NEW_NAME} --name ${OLD_NAME}
```
If you now list the clusters, you should see both the old cluster & the new cluster
Expand Down
6 changes: 3 additions & 3 deletions upup/pkg/api/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ func (c *Cluster) FillDefaults() error {
// It will be populated with the latest stable kubernetes version
func (c *Cluster) ensureKubernetesVersion() error {
if c.Spec.KubernetesVersion == "" {
latestVersion, err := findLatestKubernetesVersion()
latestVersion, err := FindLatestKubernetesVersion()
if err != nil {
return err
}
Expand All @@ -329,9 +329,9 @@ func (c *Cluster) ensureKubernetesVersion() error {
return nil
}

// findLatestKubernetesVersion returns the latest kubernetes version,
// FindLatestKubernetesVersion returns the latest kubernetes version,
// as stored at https://storage.googleapis.com/kubernetes-release/release/stable.txt
func findLatestKubernetesVersion() (string, error) {
func FindLatestKubernetesVersion() (string, error) {
stableURL := "https://storage.googleapis.com/kubernetes-release/release/stable.txt"
b, err := vfs.Context.ReadFile(stableURL)
if err != nil {
Expand Down
7 changes: 7 additions & 0 deletions upup/pkg/api/labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package api

// AnnotationNameManagement is the annotation that indicates that a cluster is under external or non-standard management
const AnnotationNameManagement = "kops.kubernetes.io/management"

// AnnotationValueManagementImported is the annotation value that indicates a cluster was imported, typically as part of an upgrade
const AnnotationValueManagementImported = "imported"
2 changes: 1 addition & 1 deletion upup/pkg/api/validation_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package api

import (
"testing"
"k8s.io/kubernetes/pkg/util/validation"
"testing"
)

func Test_Validate_DNS(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import (
"time"
)

// UpgradeCluster performs an upgrade of a k8s cluster
type UpgradeCluster struct {
// ConvertKubeupCluster performs a conversion of a cluster that was imported from kube-up
type ConvertKubeupCluster struct {
OldClusterName string
NewClusterName string
Cloud fi.Cloud
Expand All @@ -25,7 +25,7 @@ type UpgradeCluster struct {
InstanceGroups []*api.InstanceGroup
}

func (x *UpgradeCluster) Upgrade() error {
func (x *ConvertKubeupCluster) Upgrade() error {
awsCloud := x.Cloud.(*awsup.AWSCloud)

cluster := x.ClusterConfig
Expand Down Expand Up @@ -54,6 +54,11 @@ func (x *UpgradeCluster) Upgrade() error {
return fmt.Errorf("error populating cluster defaults: %v", err)
}

if cluster.Annotations != nil {
// Remove the management annotation for the new cluster
delete(cluster.Annotations, api.AnnotationNameManagement)
}

fullCluster, err := cloudup.PopulateClusterSpec(cluster, x.ClusterRegistry)
if err != nil {
return err
Expand Down
Loading

0 comments on commit 9320f6c

Please sign in to comment.