diff --git a/pkg/extensions/extensions.go b/pkg/extensions/extensions.go index 50165fbba..7ee883688 100644 --- a/pkg/extensions/extensions.go +++ b/pkg/extensions/extensions.go @@ -4,14 +4,13 @@ import ( "context" "sort" + k0sv1beta1 "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" "github.com/pkg/errors" + ecv1beta1 "github.com/replicatedhq/embedded-cluster/kinds/apis/v1beta1" "github.com/replicatedhq/embedded-cluster/pkg/config" "github.com/replicatedhq/embedded-cluster/pkg/helm" "github.com/replicatedhq/embedded-cluster/pkg/spinner" "github.com/replicatedhq/embedded-cluster/pkg/versions" - "github.com/sirupsen/logrus" - "gopkg.in/yaml.v3" - helmrepo "helm.sh/helm/v3/pkg/repo" ) func Install(ctx context.Context) error { @@ -30,24 +29,8 @@ func Install(ctx context.Context) error { return errors.Wrap(err, "create helm client") } - for _, k0sRepo := range config.AdditionalRepositories() { - logrus.Debugf("Adding helm repository %s", k0sRepo.Name) - - helmRepo := &helmrepo.Entry{ - Name: k0sRepo.Name, - URL: k0sRepo.URL, - Username: k0sRepo.Username, - Password: k0sRepo.Password, - CertFile: k0sRepo.CertFile, - KeyFile: k0sRepo.KeyFile, - CAFile: k0sRepo.CAFile, - } - if k0sRepo.Insecure != nil { - helmRepo.InsecureSkipTLSverify = *k0sRepo.Insecure - } - if err := hcli.AddRepo(helmRepo); err != nil { - return errors.Wrapf(err, "add helm repository %s", k0sRepo.Name) - } + if err := addRepos(hcli, config.AdditionalRepositories()); err != nil { + return errors.Wrap(err, "add additional helm repositories") } // sort by order first @@ -59,22 +42,66 @@ func Install(ctx context.Context) error { for _, ext := range sorted { loading.Infof("Installing %s", ext.Name) - var values map[string]interface{} - if err := yaml.Unmarshal([]byte(ext.Values), &values); err != nil { - return errors.Wrap(err, "unmarshal values") + if err := install(ctx, hcli, ext); err != nil { + return errors.Wrap(err, "install extension") } + } - _, err = hcli.Install(ctx, helm.InstallOptions{ - ReleaseName: ext.Name, - ChartPath: ext.ChartName, - ChartVersion: ext.Version, - Values: values, - Namespace: ext.TargetNS, - Timeout: ext.Timeout.Duration, - }) - if err != nil { - return errors.Wrap(err, "helm install") - } + return nil +} + +func Add(ctx context.Context, repos []k0sv1beta1.Repository, ext ecv1beta1.Chart) error { + hcli, err := helm.NewHelm(helm.HelmOptions{ + K0sVersion: versions.K0sVersion, + }) + if err != nil { + return errors.Wrap(err, "create helm client") + } + + if err := addRepos(hcli, repos); err != nil { + return errors.Wrap(err, "add repos") + } + + if err := install(ctx, hcli, ext); err != nil { + return errors.Wrap(err, "install extension") + } + + return nil +} + +func Upgrade(ctx context.Context, repos []k0sv1beta1.Repository, ext ecv1beta1.Chart) error { + hcli, err := helm.NewHelm(helm.HelmOptions{ + K0sVersion: versions.K0sVersion, + }) + if err != nil { + return errors.Wrap(err, "create helm client") + } + + if err := addRepos(hcli, repos); err != nil { + return errors.Wrap(err, "add repos") + } + + if err := upgrade(ctx, hcli, ext); err != nil { + return errors.Wrap(err, "upgrade extension") + } + + return nil +} + +func Remove(ctx context.Context, repos []k0sv1beta1.Repository, ext ecv1beta1.Chart) error { + hcli, err := helm.NewHelm(helm.HelmOptions{ + K0sVersion: versions.K0sVersion, + }) + if err != nil { + return errors.Wrap(err, "create helm client") + } + + if err := addRepos(hcli, repos); err != nil { + return errors.Wrap(err, "add repos") + } + + if err := uninstall(ctx, hcli, ext); err != nil { + return errors.Wrap(err, "uninstall extension") } return nil diff --git a/pkg/extensions/util.go b/pkg/extensions/util.go new file mode 100644 index 000000000..9c0cc7ce6 --- /dev/null +++ b/pkg/extensions/util.go @@ -0,0 +1,91 @@ +package extensions + +import ( + "context" + + k0sv1beta1 "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" + "github.com/pkg/errors" + ecv1beta1 "github.com/replicatedhq/embedded-cluster/kinds/apis/v1beta1" + "github.com/replicatedhq/embedded-cluster/pkg/helm" + "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" + helmrepo "helm.sh/helm/v3/pkg/repo" +) + +func addRepos(hcli *helm.Helm, repos []k0sv1beta1.Repository) error { + for _, r := range repos { + logrus.Debugf("Adding helm repository %s", r.Name) + + helmRepo := &helmrepo.Entry{ + Name: r.Name, + URL: r.URL, + Username: r.Username, + Password: r.Password, + CertFile: r.CertFile, + KeyFile: r.KeyFile, + CAFile: r.CAFile, + } + if r.Insecure != nil { + helmRepo.InsecureSkipTLSverify = *r.Insecure + } + if err := hcli.AddRepo(helmRepo); err != nil { + return errors.Wrapf(err, "add helm repository %s", r.Name) + } + } + + return nil +} + +func install(ctx context.Context, hcli *helm.Helm, ext ecv1beta1.Chart) error { + var values map[string]interface{} + if err := yaml.Unmarshal([]byte(ext.Values), &values); err != nil { + return errors.Wrap(err, "unmarshal values") + } + + _, err := hcli.Install(ctx, helm.InstallOptions{ + ReleaseName: ext.Name, + ChartPath: ext.ChartName, + ChartVersion: ext.Version, + Values: values, + Namespace: ext.TargetNS, + Timeout: ext.Timeout.Duration, + }) + if err != nil { + return errors.Wrap(err, "helm install") + } + + return nil +} + +func upgrade(ctx context.Context, hcli *helm.Helm, ext ecv1beta1.Chart) error { + var values map[string]interface{} + if err := yaml.Unmarshal([]byte(ext.Values), &values); err != nil { + return errors.Wrap(err, "unmarshal values") + } + + _, err := hcli.Upgrade(ctx, helm.UpgradeOptions{ + ReleaseName: ext.Name, + ChartPath: ext.ChartName, + ChartVersion: ext.Version, + Values: values, + Namespace: ext.TargetNS, + Timeout: ext.Timeout.Duration, + }) + if err != nil { + return errors.Wrap(err, "helm upgrade") + } + + return nil +} + +func uninstall(ctx context.Context, hcli *helm.Helm, ext ecv1beta1.Chart) error { + err := hcli.Uninstall(ctx, helm.UninstallOptions{ + ReleaseName: ext.Name, + Namespace: ext.TargetNS, + }) + if err != nil { + return errors.Wrap(err, "helm uninstall") + } + + return nil +} diff --git a/pkg/helm/client.go b/pkg/helm/client.go index 109f60489..de23fd087 100644 --- a/pkg/helm/client.go +++ b/pkg/helm/client.go @@ -104,6 +104,11 @@ type UpgradeOptions struct { Force bool } +type UninstallOptions struct { + ReleaseName string + Namespace string +} + type Helm struct { tmpdir string kversion *semver.Version @@ -359,6 +364,25 @@ func (h *Helm) Upgrade(ctx context.Context, opts UpgradeOptions) (*release.Relea return release, nil } +func (h *Helm) Uninstall(ctx context.Context, opts UninstallOptions) error { + cfg, err := h.getActionCfg(opts.Namespace) + if err != nil { + return fmt.Errorf("get action configuration: %w", err) + } + + client := action.NewUninstall(cfg) + + if deadline, ok := ctx.Deadline(); ok { + client.Timeout = time.Until(deadline) + } + + if _, err := client.Run(opts.ReleaseName); err != nil { + return fmt.Errorf("uninstall release: %w", err) + } + + return nil +} + func (h *Helm) Render(releaseName string, chartPath string, values map[string]interface{}, namespace string) ([][]byte, error) { cfg := &action.Configuration{} diff --git a/pkg/websocket/report.go b/pkg/websocket/report.go index afbdf037c..cdd22612a 100644 --- a/pkg/websocket/report.go +++ b/pkg/websocket/report.go @@ -12,25 +12,26 @@ import ( "github.com/sirupsen/logrus" ) -func reportUpgradeStarted(ctx context.Context, data map[string]string) { - if err := sendUpgradeReport(ctx, data, "running", ""); err != nil { +func reportStepStarted(ctx context.Context, data map[string]string) { + if err := sendStepReport(ctx, data, "running", ""); err != nil { logrus.Errorf("failed to report upgrade started: %s", err.Error()) } } -func reportUpgradeError(ctx context.Context, data map[string]string, errMsg string) { - if err := sendUpgradeReport(ctx, data, "failed", errMsg); err != nil { +func reportStepError(ctx context.Context, data map[string]string, errMsg string) { + logrus.Error(errMsg) + if err := sendStepReport(ctx, data, "failed", errMsg); err != nil { logrus.Errorf("failed to report upgrade error: %s", err.Error()) } } -func reportUpgradeSuccess(ctx context.Context, data map[string]string) { - if err := sendUpgradeReport(ctx, data, "complete", ""); err != nil { +func reportStepSuccess(ctx context.Context, data map[string]string) { + if err := sendStepReport(ctx, data, "complete", ""); err != nil { logrus.Errorf("failed to report upgrade success: %s", err.Error()) } } -func sendUpgradeReport(ctx context.Context, data map[string]string, status string, errMsg string) error { +func sendStepReport(ctx context.Context, data map[string]string, status string, errMsg string) error { reportBody := map[string]string{ "versionLabel": data["versionLabel"], "status": status, diff --git a/pkg/websocket/server.go b/pkg/websocket/server.go index e351e7cb3..464784bee 100644 --- a/pkg/websocket/server.go +++ b/pkg/websocket/server.go @@ -2,6 +2,7 @@ package websocket import ( "context" + "encoding/json" "fmt" "math/rand" "net/url" @@ -9,14 +10,15 @@ import ( "time" gwebsocket "github.com/gorilla/websocket" + k0sv1beta1 "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" "github.com/pkg/errors" ecv1beta1 "github.com/replicatedhq/embedded-cluster/kinds/apis/v1beta1" + "github.com/replicatedhq/embedded-cluster/pkg/extensions" "github.com/replicatedhq/embedded-cluster/pkg/kubeutils" "github.com/replicatedhq/embedded-cluster/pkg/upgrade" "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" - k8syaml "sigs.k8s.io/yaml" ) var wsDialer = &gwebsocket.Dialer{ @@ -100,7 +102,7 @@ func listenToWSServer(ctx context.Context, conn *gwebsocket.Conn) error { } var msg Message - if err := k8syaml.Unmarshal(message, &msg); err != nil { + if err := json.Unmarshal(message, &msg); err != nil { logrus.Errorf("failed to unmarshal message: %s: %s", err, string(message)) continue } @@ -108,29 +110,109 @@ func listenToWSServer(ctx context.Context, conn *gwebsocket.Conn) error { switch msg.Command { case "upgrade-cluster": d := map[string]string{} - if err := k8syaml.Unmarshal([]byte(msg.Data), &d); err != nil { + if err := json.Unmarshal([]byte(msg.Data), &d); err != nil { logrus.Errorf("failed to unmarshal data: %s: %s", err, string(msg.Data)) continue } - reportUpgradeStarted(ctx, d) + reportStepStarted(ctx, d) var newInstall ecv1beta1.Installation - if err := k8syaml.Unmarshal([]byte(d["installation"]), &newInstall); err != nil { - errMsg := fmt.Sprintf("failed to unmarshal installation: %s: %s", err, string(msg.Data)) - logrus.Error(errMsg) - reportUpgradeError(ctx, d, errMsg) + if err := json.Unmarshal([]byte(d["installation"]), &newInstall); err != nil { + reportStepError(ctx, d, fmt.Sprintf("failed to unmarshal installation: %s: %s", err, string(msg.Data))) continue } if err := upgrade.Upgrade(ctx, &newInstall); err != nil { - errMsg := fmt.Sprintf("failed to upgrade cluster: %s", err.Error()) - logrus.Error(errMsg) - reportUpgradeError(ctx, d, errMsg) + reportStepError(ctx, d, fmt.Sprintf("failed to upgrade cluster: %s", err.Error())) continue } - reportUpgradeSuccess(ctx, d) + reportStepSuccess(ctx, d) + + case "add-extension": + d := map[string]string{} + if err := json.Unmarshal([]byte(msg.Data), &d); err != nil { + logrus.Errorf("failed to unmarshal data: %s: %s", err, string(msg.Data)) + continue + } + + reportStepStarted(ctx, d) + + var repos []k0sv1beta1.Repository + if err := json.Unmarshal([]byte(d["repos"]), &repos); err != nil { + reportStepError(ctx, d, fmt.Sprintf("failed to unmarshal repos: %s: %s", err, string(msg.Data))) + continue + } + + var chart ecv1beta1.Chart + if err := json.Unmarshal([]byte(d["chart"]), &chart); err != nil { + reportStepError(ctx, d, fmt.Sprintf("failed to unmarshal chart: %s: %s", err, string(msg.Data))) + continue + } + + if err := extensions.Add(ctx, repos, chart); err != nil { + reportStepError(ctx, d, fmt.Sprintf("failed to add extension: %s", err.Error())) + continue + } + + reportStepSuccess(ctx, d) + + case "upgrade-extension": + d := map[string]string{} + if err := json.Unmarshal([]byte(msg.Data), &d); err != nil { + logrus.Errorf("failed to unmarshal data: %s: %s", err, string(msg.Data)) + continue + } + + reportStepStarted(ctx, d) + + var repos []k0sv1beta1.Repository + if err := json.Unmarshal([]byte(d["repos"]), &repos); err != nil { + reportStepError(ctx, d, fmt.Sprintf("failed to unmarshal repos: %s: %s", err, string(msg.Data))) + continue + } + + var chart ecv1beta1.Chart + if err := json.Unmarshal([]byte(d["chart"]), &chart); err != nil { + reportStepError(ctx, d, fmt.Sprintf("failed to unmarshal chart: %s: %s", err, string(msg.Data))) + continue + } + + if err := extensions.Upgrade(ctx, repos, chart); err != nil { + reportStepError(ctx, d, fmt.Sprintf("failed to upgrade extension: %s", err.Error())) + continue + } + + reportStepSuccess(ctx, d) + + case "remove-extension": + d := map[string]string{} + if err := json.Unmarshal([]byte(msg.Data), &d); err != nil { + logrus.Errorf("failed to unmarshal data: %s: %s", err, string(msg.Data)) + continue + } + + reportStepStarted(ctx, d) + + var repos []k0sv1beta1.Repository + if err := json.Unmarshal([]byte(d["repos"]), &repos); err != nil { + reportStepError(ctx, d, fmt.Sprintf("failed to unmarshal repos: %s: %s", err, string(msg.Data))) + continue + } + + var chart ecv1beta1.Chart + if err := json.Unmarshal([]byte(d["chart"]), &chart); err != nil { + reportStepError(ctx, d, fmt.Sprintf("failed to unmarshal chart: %s: %s", err, string(msg.Data))) + continue + } + + if err := extensions.Remove(ctx, repos, chart); err != nil { + reportStepError(ctx, d, fmt.Sprintf("failed to remove extension: %s", err.Error())) + continue + } + + reportStepSuccess(ctx, d) default: logrus.Infof("Received unknown command: %s", msg.Command) }