project restructure

Signed-off-by: aiordache <anca.iordache@docker.com>
This commit is contained in:
aiordache 2020-04-02 00:33:33 +02:00
parent 4ffc393909
commit ce3e4d7717
15 changed files with 305 additions and 78 deletions

96
compose/compose.go Normal file
View File

@ -0,0 +1,96 @@
package compose
import (
"github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile"
registry "github.com/docker/cli/cli/registry/client"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/client"
"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"
)
var (
Client client.APIClient
RegistryClient registry.RegistryClient
ConfigFile *configfile.ConfigFile
Stdout *streams.Out
)
func WithDockerCli(cli command.Cli) {
Client = cli.Client()
RegistryClient = cli.RegistryClient(false)
ConfigFile = cli.ConfigFile()
Stdout = cli.Out()
}
// 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"
)
type ProjectOptions struct {
ConfigPaths []string
Name string
}
type Project struct {
Config *types.Config
ProjectDir string
Name string `yaml:"-" json:"-"`
Orchestrator Orchestrator
}
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
}
// projectFromOptions load a compose project based on command line options
func ProjectFromOptions(options *ProjectOptions) (*Project, error) {
workingDir, configs, err := utils.GetConfigs(
options.Name,
options.ConfigPaths,
)
if err != nil {
return nil, err
}
return NewProject(types.ConfigDetails{
WorkingDir: workingDir,
ConfigFiles: configs,
Environment: utils.Environment(),
}, options.Name)
}
func (p *Project) GenerateCharts(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 (p *Project) InstallCommand(options *ProjectOptions) error {
return nil
}

View File

@ -1,23 +0,0 @@
package compose
import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile"
registry "github.com/docker/cli/cli/registry/client"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/client"
)
var (
Client client.APIClient
RegistryClient registry.RegistryClient
ConfigFile *configfile.ConfigFile
Stdout *streams.Out
)
func WithDockerCli(cli command.Cli) {
Client = cli.Client()
RegistryClient = cli.RegistryClient(false)
ConfigFile = cli.ConfigFile()
Stdout = cli.Out()
}

View File

@ -6,7 +6,6 @@ import (
"time"
"github.com/compose-spec/compose-go/types"
"github.com/docker/helm-prototype/pkg/compose"
apps "k8s.io/api/apps/v1"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -14,19 +13,19 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
)
func MapToKubernetesObjects(model *compose.Project) (map[string]runtime.Object, error) {
func MapToKubernetesObjects(model *types.Config, name string) (map[string]runtime.Object, error) {
objects := map[string]runtime.Object{}
for _, service := range model.Services {
objects[fmt.Sprintf("%s-service.yaml", service.Name)] = mapToService(model, service)
if service.Deploy != nil && service.Deploy.Mode == "global" {
daemonset, err := mapToDaemonset(service, model)
daemonset, err := mapToDaemonset(service, model, name)
if err != nil {
return nil, err
}
objects[fmt.Sprintf("%s-daemonset.yaml", service.Name)] = daemonset
} else {
deployment, err := mapToDeployment(service, model)
deployment, err := mapToDeployment(service, model, name)
if err != nil {
return nil, err
}
@ -41,7 +40,7 @@ func MapToKubernetesObjects(model *compose.Project) (map[string]runtime.Object,
return objects, nil
}
func mapToService(model *compose.Project, service types.ServiceConfig) *core.Service {
func mapToService(model *types.Config, service types.ServiceConfig) *core.Service {
ports := []core.ServicePort{}
for _, p := range service.Ports {
ports = append(ports,
@ -69,7 +68,7 @@ func mapToService(model *compose.Project, service types.ServiceConfig) *core.Ser
}
}
func mapServiceToServiceType(service types.ServiceConfig, model *compose.Project) core.ServiceType {
func mapServiceToServiceType(service types.ServiceConfig, model *types.Config) core.ServiceType {
serviceType := core.ServiceTypeClusterIP
if len(service.Networks) == 0 {
// service is implicitly attached to "default" network
@ -88,10 +87,10 @@ func mapServiceToServiceType(service types.ServiceConfig, model *compose.Project
return serviceType
}
func mapToDeployment(service types.ServiceConfig, model *compose.Project) (*apps.Deployment, error) {
func mapToDeployment(service types.ServiceConfig, model *types.Config, name string) (*apps.Deployment, error) {
labels := map[string]string{
"com.docker.compose.service": service.Name,
"com.docker.compose.project": model.Name,
"com.docker.compose.project": name,
}
podTemplate, err := toPodTemplate(service, labels, model)
if err != nil {
@ -120,10 +119,10 @@ func mapToDeployment(service types.ServiceConfig, model *compose.Project) (*apps
}, nil
}
func mapToDaemonset(service types.ServiceConfig, model *compose.Project) (*apps.DaemonSet, error) {
func mapToDaemonset(service types.ServiceConfig, model *types.Config, name string) (*apps.DaemonSet, error) {
labels := map[string]string{
"com.docker.compose.service": service.Name,
"com.docker.compose.project": model.Name,
"com.docker.compose.project": name,
}
podTemplate, err := toPodTemplate(service, labels, model)
if err != nil {

View File

@ -9,7 +9,6 @@ import (
"github.com/compose-spec/compose-go/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/helm-prototype/pkg/compose"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
@ -17,7 +16,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func toPodTemplate(serviceConfig types.ServiceConfig, labels map[string]string, model *compose.Project) (apiv1.PodTemplateSpec, error) {
func toPodTemplate(serviceConfig types.ServiceConfig, labels map[string]string, model *types.Config) (apiv1.PodTemplateSpec, error) {
tpl := apiv1.PodTemplateSpec{}
nodeAffinity, err := toNodeAffinity(serviceConfig.Deploy)
if err != nil {

View File

@ -8,13 +8,12 @@ import (
"github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types"
"github.com/docker/helm-prototype/pkg/compose"
"github.com/stretchr/testify/assert"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
func loadYAML(yaml string) (*compose.Project, error) {
func loadYAML(yaml string) (*loader.Config, error) {
dict, err := loader.ParseYAML([]byte(yaml))
if err != nil {
return nil, err
@ -23,12 +22,17 @@ func loadYAML(yaml string) (*compose.Project, error) {
if err != nil {
panic(err)
}
return compose.NewProject(types.ConfigDetails{
WorkingDir: workingDir,
ConfigFiles: []types.ConfigFile{
{Filename: "compose.yaml", Config: dict},
},
}, "test")
configs := ConfigFiles: []types.ConfigFile{
{Filename: "compose.yaml", Config: dict},
},
config := types.ConfigDetails{
WorkingDir: workingDir,
ConfigFiles: configs,
Environment: utils.Environment(),
}
model, err := loader.Load(config)
return model
}
func podTemplate(t *testing.T, yaml string) apiv1.PodTemplateSpec {
@ -38,11 +42,12 @@ func podTemplate(t *testing.T, yaml string) apiv1.PodTemplateSpec {
}
func podTemplateWithError(yaml string) (apiv1.PodTemplateSpec, error) {
project, err := loadYAML(yaml)
model, err := loadYAML(yaml)
if err != nil {
return apiv1.PodTemplateSpec{}, err
}
return toPodTemplate(project.Services[0], nil, project)
return toPodTemplate(model.Services[0], nil, model)
}
func TestToPodWithDockerSocket(t *testing.T) {

View File

@ -7,7 +7,6 @@ import (
"strings"
"github.com/compose-spec/compose-go/types"
"github.com/docker/helm-prototype/pkg/compose"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
@ -30,7 +29,7 @@ func hasPersistentVolumes(s types.ServiceConfig) bool {
return false
}
func toVolumeSpecs(s types.ServiceConfig, model *compose.Project) ([]volumeSpec, error) {
func toVolumeSpecs(s types.ServiceConfig, model *types.Config) ([]volumeSpec, error) {
var specs []volumeSpec
for i, m := range s.Volumes {
var source *apiv1.VolumeSource
@ -114,7 +113,7 @@ func or(v string, defaultValue string) string {
return defaultValue
}
func toVolumeMounts(s types.ServiceConfig, model *compose.Project) ([]apiv1.VolumeMount, error) {
func toVolumeMounts(s types.ServiceConfig, model *types.Config) ([]apiv1.VolumeMount, error) {
var mounts []apiv1.VolumeMount
specs, err := toVolumeSpecs(s, model)
if err != nil {
@ -126,7 +125,7 @@ func toVolumeMounts(s types.ServiceConfig, model *compose.Project) ([]apiv1.Volu
return mounts, nil
}
func toVolumes(s types.ServiceConfig, model *compose.Project) ([]apiv1.Volume, error) {
func toVolumes(s types.ServiceConfig, model *types.Config) ([]apiv1.Volume, error) {
var volumes []apiv1.Volume
specs, err := toVolumeSpecs(s, model)
if err != nil {

View File

@ -0,0 +1,34 @@
package helm
import (
"os"
"sync"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
type KubeConfig struct {
namespace string
config genericclioptions.RESTClientGetter
configOnce sync.Once
// KubeConfig is the path to the kubeconfig file
KubeConfig string
// KubeContext is the name of the kubeconfig context.
KubeContext string
// Bearer KubeToken used for authentication
KubeToken string
// Kubernetes API Server Endpoint for authentication
KubeAPIServer string
}
func New() *KubeConfig {
env := KubeConfig{
namespace: "",
KubeContext: os.Getenv("COMPOSE_KUBECONTEXT"),
KubeToken: os.Getenv("COMPOSE_KUBETOKEN"),
KubeAPIServer: os.Getenv("COMPOSE_KUBEAPISERVER"),
}
return &env
}

View File

@ -0,0 +1,124 @@
package utils
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types"
"github.com/prometheus/common/log"
)
var SupportedFilenames = []string{"compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml"}
func GetConfigs(name string, configPaths []string) (string, []types.ConfigFile, error) {
configPath, err := getConfigPaths(configPaths)
if err != nil {
return "", nil, err
}
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)), "")
}
configs, err := parseConfigs(configPath)
if err != nil {
return "", nil, err
}
return workingDir, configs, nil
}
func getConfigPaths(configPaths []string) ([]string, error) {
paths := []string{}
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
if len(configPaths) != 0 {
for _, f := range configPaths {
if f == "-" {
paths = append(paths, f)
continue
}
if !filepath.IsAbs(f) {
f = filepath.Join(pwd, f)
}
if _, err := os.Stat(f); err != nil {
return nil, err
}
paths = append(paths, f)
}
return paths, nil
}
sep := os.Getenv("COMPOSE_FILE_SEPARATOR")
if sep == "" {
sep = string(os.PathListSeparator)
}
f := os.Getenv("COMPOSE_FILE")
if f != "" {
return strings.Split(f, sep), nil
}
for {
candidates := []string{}
for _, n := range SupportedFilenames {
f := filepath.Join(pwd, n)
if _, err := os.Stat(f); err == nil {
candidates = append(candidates, f)
}
}
if len(candidates) > 0 {
winner := candidates[0]
if len(candidates) > 1 {
log.Warnf("Found multiple config files with supported names: %s", strings.Join(candidates, ", "))
log.Warnf("Using %s\n", winner)
}
return []string{winner}, nil
}
parent := filepath.Dir(pwd)
if parent == pwd {
return nil, fmt.Errorf("Can't find a suitable configuration file in this directory or any parent. Are you in the right directory?")
}
pwd = parent
}
}
func parseConfigs(configPaths []string) ([]types.ConfigFile, error) {
files := []types.ConfigFile{}
for _, f := range configPaths {
var (
b []byte
err error
)
if f == "-" {
b, err = ioutil.ReadAll(os.Stdin)
} else {
if _, err := os.Stat(f); err != nil {
return nil, err
}
b, err = ioutil.ReadFile(f)
}
if err != nil {
return nil, err
}
config, err := loader.ParseYAML(b)
if err != nil {
return nil, err
}
files = append(files, types.ConfigFile{Filename: f, Config: config})
}
return files, nil
}

View File

@ -0,0 +1,20 @@
package utils
import (
"os"
"strings"
)
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]
}
return m
}

View File

@ -1,11 +1,11 @@
package compose
package utils
import (
"fmt"
"strings"
)
func combine(errors []error) error {
func CombineErrors(errors []error) error {
if len(errors) == 0 {
return nil
}

View File

@ -1,4 +1,4 @@
package compose
package utils
const (
LabelDockerComposePrefix = "com.docker.compose"

View File

@ -1,26 +0,0 @@
package compose
import (
"github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types"
)
type Project struct {
types.Config
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,
projectDir: config.WorkingDir,
Name: name,
}
return &p, nil
}