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 (
"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`

View File

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

View File

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

View File

@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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