From 560014f2489fc7802a195611904d803ed6ef1212 Mon Sep 17 00:00:00 2001 From: aiordache Date: Fri, 29 Jan 2021 14:22:22 +0100 Subject: [PATCH] Add progress writer to compose up/down - remove intermediate compose interface Signed-off-by: aiordache --- kube/charts/charts.go | 120 ------------------ kube/compose.go | 76 +++++++++-- kube/context.go | 4 +- kube/{charts => }/helm/chart.go | 42 ++++++ kube/{charts => }/helm/helm.go | 70 +++++----- .../kubernetes => resources}/context.go | 2 +- kube/{charts/kubernetes => resources}/kube.go | 2 +- .../kubernetes => resources}/kube_test.go | 2 +- .../kubernetes => resources}/placement.go | 2 +- .../placement_test.go | 2 +- kube/{charts/kubernetes => resources}/pod.go | 2 +- .../kubernetes => resources}/pod_test.go | 2 +- .../kubernetes => resources}/volumes.go | 2 +- 13 files changed, 151 insertions(+), 177 deletions(-) delete mode 100644 kube/charts/charts.go rename kube/{charts => }/helm/chart.go (69%) rename kube/{charts => }/helm/helm.go (66%) rename kube/{charts/kubernetes => resources}/context.go (99%) rename kube/{charts/kubernetes => resources}/kube.go (99%) rename kube/{charts/kubernetes => resources}/kube_test.go (99%) rename kube/{charts/kubernetes => resources}/placement.go (99%) rename kube/{charts/kubernetes => resources}/placement_test.go (99%) rename kube/{charts/kubernetes => resources}/pod.go (99%) rename kube/{charts/kubernetes => resources}/pod_test.go (99%) rename kube/{charts/kubernetes => resources}/volumes.go (99%) diff --git a/kube/charts/charts.go b/kube/charts/charts.go deleted file mode 100644 index b777c5676..000000000 --- a/kube/charts/charts.go +++ /dev/null @@ -1,120 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package charts - -import ( - "context" - "path/filepath" - "strings" - - "github.com/compose-spec/compose-go/types" - "github.com/docker/compose-cli/api/compose" - apicontext "github.com/docker/compose-cli/api/context" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/kube/charts/helm" - "github.com/docker/compose-cli/kube/charts/kubernetes" - chart "helm.sh/helm/v3/pkg/chart" - util "helm.sh/helm/v3/pkg/chartutil" -) - -//SDK chart SDK -type SDK struct { - action *helm.Actions -} - -// NewSDK new chart SDK -func NewSDK(ctx context.Context) (SDK, error) { - contextStore := store.ContextStore(ctx) - currentContext := apicontext.CurrentContext(ctx) - var kubeContext store.KubeContext - - if err := contextStore.GetEndpoint(currentContext, &kubeContext); err != nil { - return SDK{}, err - } - - config, err := kubernetes.LoadConfig(kubeContext) - if err != nil { - return SDK{}, err - } - - actions, err := helm.NewHelmActions(ctx, config) - if err != nil { - return SDK{}, err - } - return SDK{ - action: actions, - }, nil -} - -// Install deploys a Compose stack -func (s SDK) Install(project *types.Project) error { - chart, err := s.GetChartInMemory(project) - if err != nil { - return err - } - return s.action.InstallChart(project.Name, chart) -} - -// Uninstall removes a runnign compose stack -func (s SDK) Uninstall(projectName string) error { - return s.action.Uninstall(projectName) -} - -// List returns a list of compose stacks -func (s SDK) List() ([]compose.Stack, error) { - return s.action.ListReleases() -} - -// GetChartInMemory get memory representation of helm chart -func (s SDK) GetChartInMemory(project *types.Project) (*chart.Chart, error) { - // replace _ with - in volume names - for k, v := range project.Volumes { - volumeName := strings.ReplaceAll(k, "_", "-") - if volumeName != k { - project.Volumes[volumeName] = v - delete(project.Volumes, k) - } - } - objects, err := kubernetes.MapToKubernetesObjects(project) - if err != nil { - return nil, err - } - //in memory files - return helm.ConvertToChart(project.Name, objects) -} - -// SaveChart converts compose project to helm and saves the chart -func (s SDK) SaveChart(project *types.Project, dest string) error { - chart, err := s.GetChartInMemory(project) - if err != nil { - return err - } - return util.SaveDir(chart, dest) -} - -// GenerateChart generates helm chart from Compose project -func (s SDK) GenerateChart(project *types.Project, dirname string) error { - if strings.Contains(dirname, ".") { - splits := strings.SplitN(dirname, ".", 2) - dirname = splits[0] - } - - dirname = filepath.Dir(dirname) - return s.SaveChart(project, dirname) -} diff --git a/kube/compose.go b/kube/compose.go index c8611f354..c648950c8 100644 --- a/kube/compose.go +++ b/kube/compose.go @@ -20,42 +20,98 @@ package kube import ( "context" + "fmt" + "strings" "github.com/compose-spec/compose-go/types" "github.com/docker/compose-cli/api/compose" + apicontext "github.com/docker/compose-cli/api/context" + "github.com/docker/compose-cli/api/context/store" "github.com/docker/compose-cli/api/errdefs" - "github.com/docker/compose-cli/kube/charts" + "github.com/docker/compose-cli/api/progress" + "github.com/docker/compose-cli/kube/helm" + "github.com/docker/compose-cli/kube/resources" ) +type composeService struct { + sdk *helm.Actions +} + // NewComposeService create a kubernetes implementation of the compose.Service API func NewComposeService(ctx context.Context) (compose.Service, error) { - chartsAPI, err := charts.NewSDK(ctx) + contextStore := store.ContextStore(ctx) + currentContext := apicontext.CurrentContext(ctx) + var kubeContext store.KubeContext + + if err := contextStore.GetEndpoint(currentContext, &kubeContext); err != nil { + return nil, err + } + config, err := resources.LoadConfig(kubeContext) + if err != nil { + return nil, err + } + actions, err := helm.NewActions(config) if err != nil { return nil, err } return &composeService{ - sdk: chartsAPI, + sdk: actions, }, nil } -type composeService struct { - sdk charts.SDK -} - // Up executes the equivalent to a `compose up` func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error { - return s.sdk.Install(project) + w := progress.ContextWriter(ctx) + + eventName := "Convert to Helm charts" + w.Event(progress.CreatingEvent(eventName)) + + chart, err := helm.GetChartInMemory(project) + if err != nil { + return err + } + w.Event(progress.NewEvent(eventName, progress.Done, "")) + + eventName = "Install Helm charts" + w.Event(progress.CreatingEvent(eventName)) + + err = s.sdk.InstallChart(project.Name, chart, func(format string, v ...interface{}) { + message := fmt.Sprintf(format, v...) + w.Event(progress.NewEvent(eventName, progress.Done, message)) + }) + + w.Event(progress.NewEvent(eventName, progress.Done, "")) + return err } // Down executes the equivalent to a `compose down` func (s *composeService) Down(ctx context.Context, projectName string, options compose.DownOptions) error { - return s.sdk.Uninstall(projectName) + w := progress.ContextWriter(ctx) + + eventName := fmt.Sprintf("Remove %s", projectName) + w.Event(progress.CreatingEvent(eventName)) + + logger := func(format string, v ...interface{}) { + message := fmt.Sprintf(format, v...) + if strings.Contains(message, "Starting delete") { + action := strings.Replace(message, "Starting delete for", "Delete", 1) + + w.Event(progress.CreatingEvent(action)) + w.Event(progress.NewEvent(action, progress.Done, "")) + return + } + w.Event(progress.NewEvent(eventName, progress.Working, message)) + } + err := s.sdk.Uninstall(projectName, logger) + w.Event(progress.NewEvent(eventName, progress.Done, "")) + + return err } // List executes the equivalent to a `docker stack ls` func (s *composeService) List(ctx context.Context) ([]compose.Stack, error) { - return s.sdk.List() + return s.sdk.ListReleases() } // Build executes the equivalent to a `compose build` diff --git a/kube/context.go b/kube/context.go index 6749441cd..f28d4a3e5 100644 --- a/kube/context.go +++ b/kube/context.go @@ -26,7 +26,7 @@ import ( "github.com/docker/compose-cli/api/errdefs" "github.com/docker/compose-cli/utils/prompt" - "github.com/docker/compose-cli/kube/charts/kubernetes" + "github.com/docker/compose-cli/kube/resources" ) // ContextParams options for creating a Kubernetes context @@ -47,7 +47,7 @@ func (cp ContextParams) CreateContextData() (interface{}, string, error) { } user := prompt.User{} selectContext := func() error { - contexts, err := kubernetes.ListAvailableKubeConfigContexts(cp.KubeConfigPath) + contexts, err := resources.ListAvailableKubeConfigContexts(cp.KubeConfigPath) if err != nil { return err } diff --git a/kube/charts/helm/chart.go b/kube/helm/chart.go similarity index 69% rename from kube/charts/helm/chart.go rename to kube/helm/chart.go index e198195cb..912de9880 100644 --- a/kube/charts/helm/chart.go +++ b/kube/helm/chart.go @@ -23,11 +23,15 @@ import ( "encoding/json" "html/template" "path/filepath" + "strings" + "github.com/compose-spec/compose-go/types" + "github.com/docker/compose-cli/kube/resources" "gopkg.in/yaml.v3" chart "helm.sh/helm/v3/pkg/chart" loader "helm.sh/helm/v3/pkg/chart/loader" + util "helm.sh/helm/v3/pkg/chartutil" "k8s.io/apimachinery/pkg/runtime" ) @@ -107,3 +111,41 @@ func jsonToYaml(j []byte, spaces int) ([]byte, error) { } return b.Bytes(), nil } + +// GetChartInMemory get memory representation of helm chart +func GetChartInMemory(project *types.Project) (*chart.Chart, error) { + // replace _ with - in volume names + for k, v := range project.Volumes { + volumeName := strings.ReplaceAll(k, "_", "-") + if volumeName != k { + project.Volumes[volumeName] = v + delete(project.Volumes, k) + } + } + objects, err := resources.MapToKubernetesObjects(project) + if err != nil { + return nil, err + } + //in memory files + return ConvertToChart(project.Name, objects) +} + +// SaveChart converts compose project to helm and saves the chart +func SaveChart(project *types.Project, dest string) error { + chart, err := GetChartInMemory(project) + if err != nil { + return err + } + return util.SaveDir(chart, dest) +} + +// GenerateChart generates helm chart from Compose project +func GenerateChart(project *types.Project, dirname string) error { + if strings.Contains(dirname, ".") { + splits := strings.SplitN(dirname, ".", 2) + dirname = splits[0] + } + + dirname = filepath.Dir(dirname) + return SaveChart(project, dirname) +} diff --git a/kube/charts/helm/helm.go b/kube/helm/helm.go similarity index 66% rename from kube/charts/helm/helm.go rename to kube/helm/helm.go index b15386f4c..87d97a174 100644 --- a/kube/charts/helm/helm.go +++ b/kube/helm/helm.go @@ -19,27 +19,25 @@ package helm import ( - "context" "errors" - "log" "github.com/docker/compose-cli/api/compose" - action "helm.sh/helm/v3/pkg/action" - chart "helm.sh/helm/v3/pkg/chart" - loader "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" env "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/release" "k8s.io/cli-runtime/pkg/genericclioptions" ) -// Actions helm actions +// Actions implements charts installation management type Actions struct { - Config *action.Configuration - Namespace string + Config *action.Configuration + Namespace string + initialize func(f func(format string, v ...interface{})) error } -// NewHelmActions new helm action -func NewHelmActions(ctx context.Context, getter genericclioptions.RESTClientGetter) (*Actions, error) { +// NewActions new helm action +func NewActions(getter genericclioptions.RESTClientGetter) (*Actions, error) { if getter == nil { settings := env.New() getter = settings.RESTClientGetter() @@ -55,40 +53,38 @@ func NewHelmActions(ctx context.Context, getter genericclioptions.RESTClientGett }, Namespace: namespace, } - err := actions.Config.Init(getter, namespace, "configmap", log.Printf) - if err != nil { - return nil, err - } - return actions, actions.Config.KubeClient.IsReachable() + actions.initialize = func(f func(format string, v ...interface{})) error { + err := actions.Config.Init(getter, namespace, "configmap", f) + if err != nil { + return err + } + return actions.Config.KubeClient.IsReachable() + } + return actions, nil } -//InstallChartFromDir install from dir -func (hc *Actions) InstallChartFromDir(name string, chartpath string) error { - chart, err := loader.Load(chartpath) +// InstallChart installs chart +func (hc *Actions) InstallChart(name string, chart *chart.Chart, logger func(format string, v ...interface{})) error { + err := hc.initialize(logger) if err != nil { return err } - return hc.InstallChart(name, chart) -} -// InstallChart instal chart -func (hc *Actions) InstallChart(name string, chart *chart.Chart) error { actInstall := action.NewInstall(hc.Config) actInstall.ReleaseName = name actInstall.Namespace = hc.Namespace - - release, err := actInstall.Run(chart, map[string]interface{}{}) - if err != nil { - return err - } - log.Println("Release status: ", release.Info.Status) - log.Println(release.Info.Description) - return nil + _, err = actInstall.Run(chart, map[string]interface{}{}) + return err } // Uninstall uninstall chart -func (hc *Actions) Uninstall(name string) error { +func (hc *Actions) Uninstall(name string, logger func(format string, v ...interface{})) error { + err := hc.initialize(logger) + if err != nil { + return err + } + release, err := hc.Get(name) if err != nil { return err @@ -97,12 +93,8 @@ func (hc *Actions) Uninstall(name string) error { return errors.New("no release found with the name provided") } actUninstall := action.NewUninstall(hc.Config) - response, err := actUninstall.Run(name) - if err != nil { - return err - } - log.Println(response.Release.Info.Description) - return nil + _, err = actUninstall.Run(name) + return err } // Get get released object for a named chart @@ -113,6 +105,10 @@ func (hc *Actions) Get(name string) (*release.Release, error) { // ListReleases lists chart releases func (hc *Actions) ListReleases() ([]compose.Stack, error) { + err := hc.initialize(nil) + if err != nil { + return nil, err + } actList := action.NewList(hc.Config) releases, err := actList.Run() if err != nil { diff --git a/kube/charts/kubernetes/context.go b/kube/resources/context.go similarity index 99% rename from kube/charts/kubernetes/context.go rename to kube/resources/context.go index e8386919d..5a40393d6 100644 --- a/kube/charts/kubernetes/context.go +++ b/kube/resources/context.go @@ -16,7 +16,7 @@ limitations under the License. */ -package kubernetes +package resources import ( "fmt" diff --git a/kube/charts/kubernetes/kube.go b/kube/resources/kube.go similarity index 99% rename from kube/charts/kubernetes/kube.go rename to kube/resources/kube.go index 4a531fa8d..20c8fa587 100644 --- a/kube/charts/kubernetes/kube.go +++ b/kube/resources/kube.go @@ -16,7 +16,7 @@ limitations under the License. */ -package kubernetes +package resources import ( "fmt" diff --git a/kube/charts/kubernetes/kube_test.go b/kube/resources/kube_test.go similarity index 99% rename from kube/charts/kubernetes/kube_test.go rename to kube/resources/kube_test.go index 148de7bd4..fdb6ef06e 100644 --- a/kube/charts/kubernetes/kube_test.go +++ b/kube/resources/kube_test.go @@ -16,7 +16,7 @@ limitations under the License. */ -package kubernetes +package resources import ( "testing" diff --git a/kube/charts/kubernetes/placement.go b/kube/resources/placement.go similarity index 99% rename from kube/charts/kubernetes/placement.go rename to kube/resources/placement.go index f5480da48..dd761f435 100644 --- a/kube/charts/kubernetes/placement.go +++ b/kube/resources/placement.go @@ -16,7 +16,7 @@ limitations under the License. */ -package kubernetes +package resources import ( "regexp" diff --git a/kube/charts/kubernetes/placement_test.go b/kube/resources/placement_test.go similarity index 99% rename from kube/charts/kubernetes/placement_test.go rename to kube/resources/placement_test.go index e3532b10b..2ca5033e8 100644 --- a/kube/charts/kubernetes/placement_test.go +++ b/kube/resources/placement_test.go @@ -16,7 +16,7 @@ limitations under the License. */ -package kubernetes +package resources import ( "reflect" diff --git a/kube/charts/kubernetes/pod.go b/kube/resources/pod.go similarity index 99% rename from kube/charts/kubernetes/pod.go rename to kube/resources/pod.go index e2acf0533..ebb7873d4 100644 --- a/kube/charts/kubernetes/pod.go +++ b/kube/resources/pod.go @@ -16,7 +16,7 @@ limitations under the License. */ -package kubernetes +package resources import ( "fmt" diff --git a/kube/charts/kubernetes/pod_test.go b/kube/resources/pod_test.go similarity index 99% rename from kube/charts/kubernetes/pod_test.go rename to kube/resources/pod_test.go index f0d139436..4970fc259 100644 --- a/kube/charts/kubernetes/pod_test.go +++ b/kube/resources/pod_test.go @@ -16,7 +16,7 @@ limitations under the License. */ -package kubernetes +package resources import ( "fmt" diff --git a/kube/charts/kubernetes/volumes.go b/kube/resources/volumes.go similarity index 99% rename from kube/charts/kubernetes/volumes.go rename to kube/resources/volumes.go index 0e331ac59..aff187ced 100644 --- a/kube/charts/kubernetes/volumes.go +++ b/kube/resources/volumes.go @@ -16,7 +16,7 @@ limitations under the License. */ -package kubernetes +package resources import ( "fmt"