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).
*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
@ -211,6 +211,18 @@ To remove Dapr from your Kubernetes cluster, use the `uninstall` command with `-
$ 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
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
helm.sh/helm/v3 v3.4.0
k8s.io/api v0.20.0
k8s.io/apiextensions-apiserver v0.20.0
k8s.io/apimachinery v0.20.0
k8s.io/cli-runtime 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)
}