Merge pull request #1221 from aiordache/kube_progress_writer

Kube backend: Add progress writer to `compose up/down`
This commit is contained in:
Guillaume Tardif 2021-02-03 12:01:03 +01:00 committed by GitHub
commit 7d0be7ad5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 151 additions and 177 deletions

View File

@ -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)
}

View File

@ -20,42 +20,98 @@ package kube
import ( import (
"context" "context"
"fmt"
"strings"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/docker/compose-cli/api/compose" "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/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 // NewComposeService create a kubernetes implementation of the compose.Service API
func NewComposeService(ctx context.Context) (compose.Service, error) { 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 { if err != nil {
return nil, err return nil, err
} }
return &composeService{ return &composeService{
sdk: chartsAPI, sdk: actions,
}, nil }, nil
} }
type composeService struct {
sdk charts.SDK
}
// Up executes the equivalent to a `compose up` // Up executes the equivalent to a `compose up`
func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error { 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` // Down executes the equivalent to a `compose down`
func (s *composeService) Down(ctx context.Context, projectName string, options compose.DownOptions) error { 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` // List executes the equivalent to a `docker stack ls`
func (s *composeService) List(ctx context.Context) ([]compose.Stack, error) { 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` // Build executes the equivalent to a `compose build`

View File

@ -26,7 +26,7 @@ import (
"github.com/docker/compose-cli/api/errdefs" "github.com/docker/compose-cli/api/errdefs"
"github.com/docker/compose-cli/utils/prompt" "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 // ContextParams options for creating a Kubernetes context
@ -47,7 +47,7 @@ func (cp ContextParams) CreateContextData() (interface{}, string, error) {
} }
user := prompt.User{} user := prompt.User{}
selectContext := func() error { selectContext := func() error {
contexts, err := kubernetes.ListAvailableKubeConfigContexts(cp.KubeConfigPath) contexts, err := resources.ListAvailableKubeConfigContexts(cp.KubeConfigPath)
if err != nil { if err != nil {
return err return err
} }

View File

@ -23,11 +23,15 @@ import (
"encoding/json" "encoding/json"
"html/template" "html/template"
"path/filepath" "path/filepath"
"strings"
"github.com/compose-spec/compose-go/types"
"github.com/docker/compose-cli/kube/resources"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
chart "helm.sh/helm/v3/pkg/chart" chart "helm.sh/helm/v3/pkg/chart"
loader "helm.sh/helm/v3/pkg/chart/loader" loader "helm.sh/helm/v3/pkg/chart/loader"
util "helm.sh/helm/v3/pkg/chartutil"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -107,3 +111,41 @@ func jsonToYaml(j []byte, spaces int) ([]byte, error) {
} }
return b.Bytes(), nil 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)
}

View File

@ -19,27 +19,25 @@
package helm package helm
import ( import (
"context"
"errors" "errors"
"log"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
action "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
chart "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
loader "helm.sh/helm/v3/pkg/chart/loader"
env "helm.sh/helm/v3/pkg/cli" env "helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
) )
// Actions helm actions // Actions implements charts installation management
type Actions struct { type Actions struct {
Config *action.Configuration Config *action.Configuration
Namespace string Namespace string
initialize func(f func(format string, v ...interface{})) error
} }
// NewHelmActions new helm action // NewActions new helm action
func NewHelmActions(ctx context.Context, getter genericclioptions.RESTClientGetter) (*Actions, error) { func NewActions(getter genericclioptions.RESTClientGetter) (*Actions, error) {
if getter == nil { if getter == nil {
settings := env.New() settings := env.New()
getter = settings.RESTClientGetter() getter = settings.RESTClientGetter()
@ -55,40 +53,38 @@ func NewHelmActions(ctx context.Context, getter genericclioptions.RESTClientGett
}, },
Namespace: namespace, 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 // InstallChart installs chart
func (hc *Actions) InstallChartFromDir(name string, chartpath string) error { func (hc *Actions) InstallChart(name string, chart *chart.Chart, logger func(format string, v ...interface{})) error {
chart, err := loader.Load(chartpath) err := hc.initialize(logger)
if err != nil { if err != nil {
return err 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 := action.NewInstall(hc.Config)
actInstall.ReleaseName = name actInstall.ReleaseName = name
actInstall.Namespace = hc.Namespace actInstall.Namespace = hc.Namespace
_, err = actInstall.Run(chart, map[string]interface{}{})
release, err := actInstall.Run(chart, map[string]interface{}{}) return err
if err != nil {
return err
}
log.Println("Release status: ", release.Info.Status)
log.Println(release.Info.Description)
return nil
} }
// Uninstall uninstall chart // 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) release, err := hc.Get(name)
if err != nil { if err != nil {
return err return err
@ -97,12 +93,8 @@ func (hc *Actions) Uninstall(name string) error {
return errors.New("no release found with the name provided") return errors.New("no release found with the name provided")
} }
actUninstall := action.NewUninstall(hc.Config) actUninstall := action.NewUninstall(hc.Config)
response, err := actUninstall.Run(name) _, err = actUninstall.Run(name)
if err != nil { return err
return err
}
log.Println(response.Release.Info.Description)
return nil
} }
// Get get released object for a named chart // 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 // ListReleases lists chart releases
func (hc *Actions) ListReleases() ([]compose.Stack, error) { func (hc *Actions) ListReleases() ([]compose.Stack, error) {
err := hc.initialize(nil)
if err != nil {
return nil, err
}
actList := action.NewList(hc.Config) actList := action.NewList(hc.Config)
releases, err := actList.Run() releases, err := actList.Run()
if err != nil { if err != nil {

View File

@ -16,7 +16,7 @@
limitations under the License. limitations under the License.
*/ */
package kubernetes package resources
import ( import (
"fmt" "fmt"

View File

@ -16,7 +16,7 @@
limitations under the License. limitations under the License.
*/ */
package kubernetes package resources
import ( import (
"fmt" "fmt"

View File

@ -16,7 +16,7 @@
limitations under the License. limitations under the License.
*/ */
package kubernetes package resources
import ( import (
"testing" "testing"

View File

@ -16,7 +16,7 @@
limitations under the License. limitations under the License.
*/ */
package kubernetes package resources
import ( import (
"regexp" "regexp"

View File

@ -16,7 +16,7 @@
limitations under the License. limitations under the License.
*/ */
package kubernetes package resources
import ( import (
"reflect" "reflect"

View File

@ -16,7 +16,7 @@
limitations under the License. limitations under the License.
*/ */
package kubernetes package resources
import ( import (
"fmt" "fmt"

View File

@ -16,7 +16,7 @@
limitations under the License. limitations under the License.
*/ */
package kubernetes package resources
import ( import (
"fmt" "fmt"

View File

@ -16,7 +16,7 @@
limitations under the License. limitations under the License.
*/ */
package kubernetes package resources
import ( import (
"fmt" "fmt"