diff --git a/compose/compose.go b/compose/compose.go index d7a40d781..2f4f5fef1 100644 --- a/compose/compose.go +++ b/compose/compose.go @@ -1,28 +1,9 @@ package compose import ( - "log" + "os" - chartloader "helm.sh/helm/v3/pkg/chart/loader" - - "github.com/compose-spec/compose-go/loader" - "github.com/compose-spec/compose-go/types" - "github.com/docker/helm-prototype/pkg/compose/internal/convert" - "github.com/docker/helm-prototype/pkg/compose/internal/helm" - utils "github.com/docker/helm-prototype/pkg/compose/internal/utils" - "helm.sh/helm/v3/pkg/action" - env "helm.sh/helm/v3/pkg/cli" - k "helm.sh/helm/v3/pkg/kube" -) - -// Orchestrator is "kubernetes" or "swarm" -type Orchestrator string - -const ( - // Kubernetes specifies to use kubernetes. - Kubernetes Orchestrator = "kubernetes" - // Swarm specifies to use Docker swarm. - Swarm Orchestrator = "swarm" + internal "github.com/docker/helm-prototype/pkg/compose/internal" ) type ProjectOptions struct { @@ -30,107 +11,48 @@ type ProjectOptions struct { Name string } -type Project struct { - Config *types.Config - ProjectDir string - Name string `yaml:"-" json:"-"` - Orchestrator Orchestrator -} +var Settings = internal.GetDefault() -func NewProject(config types.ConfigDetails, name string) (*Project, error) { - model, err := loader.Load(config) - if err != nil { - return nil, err - } - - p := Project{ - Config: model, - ProjectDir: config.WorkingDir, - Name: name, - } - return &p, nil +type ComposeAPI struct { + project *internal.Project } // projectFromOptions load a compose project based on command line options -func ProjectFromOptions(options *ProjectOptions) (*Project, error) { +func ProjectFromOptions(options *ProjectOptions) (*ComposeAPI, error) { + if options == nil { + options = &ProjectOptions{ + ConfigPaths: []string{}, + Name: "docker-compose", + } + } + if options.Name == "" { options.Name = "docker-compose" } - workingDir, configs, err := utils.GetConfigs( - options.Name, - options.ConfigPaths, - ) + project, err := internal.GetProject(options.Name, options.ConfigPaths) if err != nil { return nil, err } - return NewProject(types.ConfigDetails{ - WorkingDir: workingDir, - ConfigFiles: configs, - Environment: utils.Environment(), - }, options.Name) + return &ComposeAPI{project: project}, nil } -func (p *Project) GenerateChart(path string) error { - objects, err := convert.MapToKubernetesObjects(p.Config, p.Name) - if err != nil { - return err - } - err = helm.Write(p.Name, objects, path) - if err != nil { - return err - } - return nil +func (c *ComposeAPI) GenerateChart(dirname string) error { + return c.project.ExportToCharts(dirname) } -func (p *Project) InstallChart(n, path string) error { - +func (c *ComposeAPI) Install(name, path string) error { if path == "" { - err := p.GenerateChart(path) + cwd, err := os.Getwd() if err != nil { return err } + path = cwd } - - settings := env.New() - actionConfig := new(action.Configuration) - println(".......... here ............") - if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), "memory", nil); err != nil { - log.Fatal(err) - } - println(settings.EnvVars()) - client := action.NewInstall(actionConfig) - println("Original chart version:", client.Version) - client.Version = ">0.0.0-0" - - name, chart, err := client.NameAndChart([]string{n, path}) - if err != nil { - return nil - } - - client.ReleaseName = name - client.Namespace = settings.Namespace() - cp, err := client.ChartPathOptions.LocateChart(chart, settings) - if err != nil { - return err - } - println("CHART PATH: ", cp) - - chartRequested, err := chartloader.Load(cp) - if err != nil { - return err - } - kclient := k.New(settings.RESTClientGetter()) - println(kclient.Namespace) - if err = actionConfig.KubeClient.IsReachable(); err != nil { - println("Kube API is not reachable") - return err - } - println("....Running.....") - println("Chart description: ", chartRequested.Metadata.Description) - release, err := client.Run(chartRequested, map[string]interface{}{}) - - println(release.Name) - return err + return c.project.Install(name, path) +} + +func (c *ComposeAPI) Uninstall(name string) error { + return c.project.Uninstall(name) } diff --git a/compose/internal/helm/chart.go b/compose/internal/helm/chart.go new file mode 100644 index 000000000..36f7e4487 --- /dev/null +++ b/compose/internal/helm/chart.go @@ -0,0 +1,70 @@ +package helm + +import ( + "gopkg.in/yaml.v2" + action "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + loader "helm.sh/helm/v3/pkg/chart/loader" +) + +type HelmChart struct { + chart *chart.Chart + actionConfig *HelmConfig + Path string + Name string +} + +func NewChart(name, chartpath string) *HelmChart { + chart, err := loader.Load(chartpath) + if err != nil { + return nil + } + return &HelmChart{ + chart: chart, + Path: chartpath, + Name: name, + } +} +func (chart *HelmChart) SetActionConfig(config *HelmConfig) error { + chart.actionConfig = config + return nil +} + +func (chart *HelmChart) Validate() error { + _, err := yaml.Marshal(chart.chart.Metadata) + if err != nil { + return nil + } + + return nil +} + +func (chart *HelmChart) Install() error { + err := chart.actionConfig.InitKubeClient() + if err != nil { + return err + } + + actInstall := action.NewInstall(chart.actionConfig.Config) + actInstall.ReleaseName = chart.Name + actInstall.Namespace = chart.actionConfig.Settings.Namespace() + + release, err := actInstall.Run(chart.chart, map[string]interface{}{}) + if err != nil { + return err + } + + println("Release status: ", release.Info.Status) + println("Release description: ", release.Info.Description) + return chart.actionConfig.Config.Releases.Update(release) +} + +func (chart *HelmChart) Uninstall() error { + err := chart.actionConfig.InitKubeClient() + if err != nil { + return err + } + actUninstall := action.NewUninstall(chart.actionConfig.Config) + _, err = actUninstall.Run(chart.Name) + return err +} diff --git a/compose/internal/helm/helm.go b/compose/internal/helm/helm.go new file mode 100644 index 000000000..b73a81ec5 --- /dev/null +++ b/compose/internal/helm/helm.go @@ -0,0 +1,39 @@ +package helm + +import ( + "log" + + action "helm.sh/helm/v3/pkg/action" + env "helm.sh/helm/v3/pkg/cli" +) + +type HelmConfig struct { + Config *action.Configuration + Settings *env.EnvSettings + kube_conn_init bool +} + +func NewHelmConfig(settings *env.EnvSettings) *HelmConfig { + if settings == nil { + settings = env.New() + } + return &HelmConfig{ + Config: new(action.Configuration), + Settings: settings, + kube_conn_init: false, + } +} + +func (hc *HelmConfig) InitKubeClient() error { + if hc.kube_conn_init { + return nil + } + if err := hc.Config.Init(hc.Settings.RESTClientGetter(), hc.Settings.Namespace(), "memory", log.Printf); err != nil { + log.Fatal(err) + } + if err := hc.Config.KubeClient.IsReachable(); err != nil { + return err + } + hc.kube_conn_init = true + return nil +} diff --git a/compose/internal/convert/kube.go b/compose/internal/kube/kube.go similarity index 99% rename from compose/internal/convert/kube.go rename to compose/internal/kube/kube.go index 884d56935..75d44842a 100644 --- a/compose/internal/convert/kube.go +++ b/compose/internal/kube/kube.go @@ -1,4 +1,4 @@ -package convert +package kube import ( "fmt" diff --git a/compose/internal/convert/placement.go b/compose/internal/kube/placement.go similarity index 99% rename from compose/internal/convert/placement.go rename to compose/internal/kube/placement.go index c757e0a5e..cb56ca7ce 100644 --- a/compose/internal/convert/placement.go +++ b/compose/internal/kube/placement.go @@ -1,4 +1,4 @@ -package convert +package kube import ( "regexp" diff --git a/compose/internal/convert/placement_test.go b/compose/internal/kube/placement_test.go similarity index 99% rename from compose/internal/convert/placement_test.go rename to compose/internal/kube/placement_test.go index 1169d3c85..77d3d6db5 100644 --- a/compose/internal/convert/placement_test.go +++ b/compose/internal/kube/placement_test.go @@ -1,4 +1,4 @@ -package convert +package kube import ( "reflect" diff --git a/compose/internal/convert/pod.go b/compose/internal/kube/pod.go similarity index 99% rename from compose/internal/convert/pod.go rename to compose/internal/kube/pod.go index 2842bba54..e0ef2614e 100644 --- a/compose/internal/convert/pod.go +++ b/compose/internal/kube/pod.go @@ -1,4 +1,4 @@ -package convert +package kube import ( "fmt" diff --git a/compose/internal/convert/pod_test.go b/compose/internal/kube/pod_test.go similarity index 99% rename from compose/internal/convert/pod_test.go rename to compose/internal/kube/pod_test.go index 078a8f839..7d9cc1b67 100644 --- a/compose/internal/convert/pod_test.go +++ b/compose/internal/kube/pod_test.go @@ -1,4 +1,4 @@ -package convert +package kube import ( "fmt" diff --git a/compose/internal/convert/volumes.go b/compose/internal/kube/volumes.go similarity index 99% rename from compose/internal/convert/volumes.go rename to compose/internal/kube/volumes.go index bcc38eb3c..b814280f9 100644 --- a/compose/internal/convert/volumes.go +++ b/compose/internal/kube/volumes.go @@ -1,4 +1,4 @@ -package convert +package kube import ( "fmt" diff --git a/compose/internal/project.go b/compose/internal/project.go new file mode 100644 index 000000000..f35515482 --- /dev/null +++ b/compose/internal/project.go @@ -0,0 +1,112 @@ +package project + +import ( + "github.com/compose-spec/compose-go/loader" + "github.com/compose-spec/compose-go/types" + "github.com/docker/helm-prototype/pkg/compose/internal/helm" + "github.com/docker/helm-prototype/pkg/compose/internal/utils" + + "github.com/docker/helm-prototype/pkg/compose/internal/kube" +) + +// Kind is "kubernetes" or "docker" +type Kind string + +const ( + // Kubernetes specifies to use a kubernetes cluster. + Kubernetes Kind = "kubernetes" + // Docker specifies to use Docker engine. + DockerEngine Kind = "docker" +) + +type Engine struct { + Namespace string + + Kind Kind + + Config string + // Context is the name of the kubeconfig/docker context. + Context string + // Token used for authentication (kubernetes) + Token string + // Kubernetes API Server Endpoint for authentication + APIServer string +} + +func GetDefault() *Engine { + return &Engine{Kind: Kubernetes} +} + +type Project struct { + Config *types.Config + HelmConfig *helm.HelmConfig + HelmChart *helm.HelmChart + ProjectDir string + Name string `yaml:"-" json:"-"` +} + +func NewProject(config types.ConfigDetails, name string) (*Project, error) { + model, err := loader.Load(config) + if err != nil { + return nil, err + } + + p := Project{ + Config: model, + HelmConfig: helm.NewHelmConfig(nil), + HelmChart: nil, + ProjectDir: config.WorkingDir, + Name: name, + } + return &p, nil +} + +func GetProject(name string, configPaths []string) (*Project, error) { + if name == "" { + name = "docker-compose" + } + + workingDir, configs, err := utils.GetConfigs( + name, + configPaths, + ) + if err != nil { + return nil, err + } + + return NewProject(types.ConfigDetails{ + WorkingDir: workingDir, + ConfigFiles: configs, + Environment: utils.Environment(), + }, name) + +} + +func (p *Project) ExportToCharts(path string) error { + objects, err := kube.MapToKubernetesObjects(p.Config, p.Name) + if err != nil { + return err + } + err = helm.Write(p.Name, objects, path) + if err != nil { + return err + } + return nil +} + +func (p *Project) Install(name, path string) error { + if p.HelmChart == nil { + chart := helm.NewChart(name, path) + chart.SetActionConfig(p.HelmConfig) + p.HelmChart = chart + } + return p.HelmChart.Install() +} + +func (p *Project) Uninstall(name string) error { + if p.HelmChart == nil { + p.HelmChart = helm.NewChart(name, "") + p.HelmChart.SetActionConfig(p.HelmConfig) + } + return p.HelmChart.Uninstall() +} diff --git a/compose/internal/utils/config.go b/compose/internal/utils/config.go index 12974553b..93baa91da 100644 --- a/compose/internal/utils/config.go +++ b/compose/internal/utils/config.go @@ -20,13 +20,11 @@ func GetConfigs(name string, configPaths []string) (string, []types.ConfigFile, if err != nil { return "", nil, err } + workingDir := filepath.Dir(configPath[0]) if name == "" { name = os.Getenv("COMPOSE_PROJECT_NAME") } - - workingDir := filepath.Dir(configPath[0]) - if name == "" { r := regexp.MustCompile(`[^a-z0-9\\-_]+`) name = r.ReplaceAllString(strings.ToLower(filepath.Base(workingDir)), "") diff --git a/compose/internal/utils/env.go b/compose/internal/utils/env.go index 3044742bb..e7673fef0 100644 --- a/compose/internal/utils/env.go +++ b/compose/internal/utils/env.go @@ -6,15 +6,11 @@ import ( ) func Environment() map[string]string { - return getAsEqualsMap(os.Environ()) -} - -// getAsEqualsMap split key=value formatted strings into a key : value map -func getAsEqualsMap(em []string) map[string]string { - m := make(map[string]string) - for _, v := range em { - kv := strings.SplitN(v, "=", 2) - m[kv[0]] = kv[1] + vars := make(map[string]string) + env := os.Environ() + for _, v := range env { + k := strings.SplitN(v, "=", 2) + vars[k[0]] = k[1] } - return m + return vars }