Add dapr upgrade command (#566)

* add dapr upgrade command

* make flags required and update README

* linter

* remove hard-coded namespace

* add unit tests

* remove default runtime-version for upgrade

* seperate ha from mtls

* allow upgrading helm installations
This commit is contained in:
Yaron Schneider 2021-01-06 11:36:20 -08:00 committed by GitHub
parent 790b349e43
commit 6d90ec1c77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 268 additions and 1 deletions

View File

@ -173,7 +173,7 @@ $ dapr uninstall --network dapr-network
The init command will install Dapr to a Kubernetes cluster. For more advanced use cases, use our [Helm Chart](https://github.com/dapr/dapr/tree/master/charts/dapr). The init command will install Dapr to a Kubernetes cluster. For more advanced use cases, use our [Helm Chart](https://github.com/dapr/dapr/tree/master/charts/dapr).
*Note: The default namespace is dapr-system* *Note: The default namespace is dapr-system. The installation will appear under the name `dapr` for Helm*
``` ```
$ dapr init -k $ dapr init -k
@ -211,6 +211,18 @@ To remove Dapr from your Kubernetes cluster, use the `uninstall` command with `-
$ dapr uninstall -k $ dapr uninstall -k
``` ```
### Upgrade Dapr on Kubernetes
To perform a zero downtime upgrade of the Dapr control plane:
```
$ dapr upgrade -k --runtime-version=1.0.0-rc.2
```
The example above shows how to upgrade from your current version to version `1.0.0-rc.2`.
*Note: do not use the `dapr upgrade` command if you're upgrading from 0.x versions of Dapr*
### Launch Dapr and your app ### Launch Dapr and your app
The Dapr CLI lets you debug easily by launching both Dapr and your app. The Dapr CLI lets you debug easily by launching both Dapr and your app.

46
cmd/upgrade.go Normal file
View File

@ -0,0 +1,46 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package cmd
import (
"os"
"github.com/dapr/cli/pkg/kubernetes"
"github.com/dapr/cli/pkg/print"
"github.com/spf13/cobra"
)
var UpgradeCmd = &cobra.Command{
Use: "upgrade",
Short: "Upgrades a Dapr control plane installation in a cluster. Supported platforms: Kubernetes",
Example: `
# Upgrade Dapr in Kubernetes
dapr upgrade -k
# See more at: https://docs.dapr.io/getting-started/
`,
Run: func(cmd *cobra.Command, args []string) {
err := kubernetes.Upgrade(kubernetes.UpgradeConfig{
RuntimeVersion: runtimeVersion,
})
if err != nil {
print.FailureStatusEvent(os.Stdout, "Failed to upgrade Dapr: %s", err)
return
}
print.SuccessStatusEvent(os.Stdout, "Dapr control plane successfully upgraded to version %s. Make sure your deployments are restarted to pick up the latest sidecar version.", runtimeVersion)
},
}
func init() {
UpgradeCmd.Flags().BoolVarP(&kubernetesMode, "kubernetes", "k", false, "Upgrade Dapr in a Kubernetes cluster")
UpgradeCmd.Flags().StringVarP(&runtimeVersion, "runtime-version", "", "", "The version of the Dapr runtime to upgrade to, for example: 1.0.0")
UpgradeCmd.Flags().BoolP("help", "h", false, "Print this help message")
UpgradeCmd.MarkFlagRequired("runtime-version")
UpgradeCmd.MarkFlagRequired("kubernetes")
RootCmd.AddCommand(UpgradeCmd)
}

1
go.mod
View File

@ -26,6 +26,7 @@ require (
gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v2 v2.3.0
helm.sh/helm/v3 v3.4.0 helm.sh/helm/v3 v3.4.0
k8s.io/api v0.20.0 k8s.io/api v0.20.0
k8s.io/apiextensions-apiserver v0.20.0
k8s.io/apimachinery v0.20.0 k8s.io/apimachinery v0.20.0
k8s.io/cli-runtime v0.20.0 k8s.io/cli-runtime v0.20.0
k8s.io/client-go v0.20.0 k8s.io/client-go v0.20.0

166
pkg/kubernetes/upgrade.go Normal file
View File

@ -0,0 +1,166 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package kubernetes
import (
"errors"
"fmt"
"os"
"strings"
"github.com/dapr/cli/pkg/print"
"github.com/dapr/cli/utils"
helm "helm.sh/helm/v3/pkg/action"
"k8s.io/helm/pkg/strvals"
)
const operatorName = "dapr-operator"
var crds = []string{
"components",
"configuration",
"subscription",
}
type UpgradeConfig struct {
RuntimeVersion string
}
func Upgrade(conf UpgradeConfig) error {
sc, err := NewStatusClient()
if err != nil {
return err
}
status, err := sc.Status()
if err != nil {
return err
}
if len(status) == 0 {
return errors.New("dapr is not installed in your cluster")
}
var daprVersion string
for _, s := range status {
if s.Name == operatorName {
daprVersion = s.Version
}
}
print.InfoStatusEvent(os.Stdout, "Dapr control plane version %s detected in namespace %s", daprVersion, status[0].Namespace)
helmConf, err := helmConfig(status[0].Namespace)
if err != nil {
return err
}
daprChart, err := daprChart(conf.RuntimeVersion, helmConf)
if err != nil {
return err
}
upgradeClient := helm.NewUpgrade(helmConf)
upgradeClient.ResetValues = true
upgradeClient.Namespace = status[0].Namespace
upgradeClient.CleanupOnFail = true
upgradeClient.Wait = true
print.InfoStatusEvent(os.Stdout, "Starting upgrade...")
mtls, err := IsMTLSEnabled()
if err != nil {
return err
}
var vals map[string]interface{}
var ca []byte
var issuerCert []byte
var issuerKey []byte
if mtls {
secret, sErr := getTrustChainSecret()
if sErr != nil {
return sErr
}
ca = secret.Data["ca.crt"]
issuerCert = secret.Data["issuer.crt"]
issuerKey = secret.Data["issuer.key"]
}
ha := highAvailabilityEnabled(status)
vals, err = upgradeChartValues(string(ca), string(issuerCert), string(issuerKey), ha)
if err != nil {
return err
}
err = applyCRDs(fmt.Sprintf("v%s", conf.RuntimeVersion))
if err != nil {
return err
}
listClient := helm.NewList(helmConf)
releases, err := listClient.Run()
if err != nil {
return err
}
var chart string
for _, r := range releases {
if r.Chart != nil && strings.Contains(r.Chart.Name(), "dapr") {
chart = r.Name
break
}
}
if _, err = upgradeClient.Run(chart, daprChart, vals); err != nil {
return err
}
return nil
}
func highAvailabilityEnabled(status []StatusOutput) bool {
for _, s := range status {
if s.Replicas > 1 {
return true
}
}
return false
}
func applyCRDs(version string) error {
for _, crd := range crds {
url := fmt.Sprintf("https://raw.githubusercontent.com/dapr/dapr/%s/charts/dapr/crds/%s.yaml", version, crd)
_, err := utils.RunCmdAndWait("kubectl", "apply", "-f", url)
if err != nil {
return err
}
}
return nil
}
func upgradeChartValues(ca, issuerCert, issuerKey string, haMode bool) (map[string]interface{}, error) {
chartVals := map[string]interface{}{}
globalVals := []string{}
if ca != "" && issuerCert != "" && issuerKey != "" {
globalVals = append(globalVals, fmt.Sprintf("dapr_sentry.tls.root.certPEM=%s", ca),
fmt.Sprintf("dapr_sentry.tls.issuer.certPEM=%s", issuerCert),
fmt.Sprintf("dapr_sentry.tls.issuer.keyPEM=%s", issuerKey),
)
}
if haMode {
globalVals = append(globalVals, "global.ha.enabled=true")
}
for _, v := range globalVals {
if err := strvals.ParseInto(v, chartVals); err != nil {
return nil, err
}
}
return chartVals, nil
}

View File

@ -0,0 +1,42 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package kubernetes
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHAMode(t *testing.T) {
t.Run("ha mode", func(t *testing.T) {
s := []StatusOutput{
{
Replicas: 3,
},
}
r := highAvailabilityEnabled(s)
assert.True(t, r)
})
t.Run("non-ha mode", func(t *testing.T) {
s := []StatusOutput{
{
Replicas: 1,
},
}
r := highAvailabilityEnabled(s)
assert.False(t, r)
})
}
func TestMTLSChartValues(t *testing.T) {
val, err := upgradeChartValues("1", "2", "3", true)
assert.NoError(t, err)
assert.Len(t, val, 2)
}