Replicate docker-compose file loading

Skeletton to produce helm chart by internal conversion

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2020-02-19 12:17:16 +01:00 committed by aiordache
parent fea9697d77
commit ba43317862
6 changed files with 272 additions and 0 deletions

23
compose/docker.go Normal file
View File

@ -0,0 +1,23 @@
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()
}

38
compose/errors.go Normal file
View File

@ -0,0 +1,38 @@
package compose
import (
"fmt"
"strings"
)
func combine(errors []error) error {
if len(errors) == 0 {
return nil
}
if len(errors) == 1 {
return errors[0]
}
err := combinedError{}
for _, e := range errors {
if c, ok := e.(combinedError); ok {
err.errors = append(err.errors, c.errors...)
} else {
err.errors = append(err.errors, e)
}
}
return combinedError{errors}
}
type combinedError struct {
errors []error
}
func (c combinedError) Error() string {
points := make([]string, len(c.errors))
for i, err := range c.errors {
points[i] = fmt.Sprintf("* %s", err.Error())
}
return fmt.Sprintf(
"%d errors occurred:\n\t%s",
len(c.errors), strings.Join(points, "\n\t"))
}

17
compose/labels.go Normal file
View File

@ -0,0 +1,17 @@
package compose
const (
LABEL_DOCKER_COMPOSE_PREFIX = "com.docker.compose"
LABEL_SERVICE = LABEL_DOCKER_COMPOSE_PREFIX + ".service"
LABEL_VERSION = LABEL_DOCKER_COMPOSE_PREFIX + ".version"
LABEL_CONTAINER_NUMBER = LABEL_DOCKER_COMPOSE_PREFIX + ".container-number"
LABEL_ONE_OFF = LABEL_DOCKER_COMPOSE_PREFIX + ".oneoff"
LABEL_NETWORK = LABEL_DOCKER_COMPOSE_PREFIX + ".network"
LABEL_SLUG = LABEL_DOCKER_COMPOSE_PREFIX + ".slug"
LABEL_VOLUME = LABEL_DOCKER_COMPOSE_PREFIX + ".volume"
LABEL_CONFIG_HASH = LABEL_DOCKER_COMPOSE_PREFIX + ".config-hash"
LABEL_PROJECT = LABEL_DOCKER_COMPOSE_PREFIX + ".project"
LABEL_WORKING_DIR = LABEL_DOCKER_COMPOSE_PREFIX + ".working_dir"
LABEL_CONFIG_FILES = LABEL_DOCKER_COMPOSE_PREFIX + ".config_files"
LABEL_ENVIRONMENT_FILE = LABEL_DOCKER_COMPOSE_PREFIX + ".environment_file"
)

26
compose/project.go Normal file
View File

@ -0,0 +1,26 @@
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
}

96
helm/output.go Normal file
View File

@ -0,0 +1,96 @@
package helm
import (
"bytes"
"encoding/json"
"gopkg.in/yaml.v3"
"html/template"
"io/ioutil"
"k8s.io/apimachinery/pkg/runtime"
"os"
"path/filepath"
)
func Write(project string, objects map[string]runtime.Object, target string) error {
out := Outputer{ target }
if err := out.Write("README.md", []byte("This chart was created by converting a Compose file")); err != nil {
return err
}
chart := `name: {{.Name}}
description: A generated Helm Chart for {{.Name}} from Skippbox Kompose
version: 0.0.1
apiVersion: v1
keywords:
- {{.Name}}
sources:
home:
`
t, err := template.New("ChartTmpl").Parse(chart)
if err != nil {
return err
}
type ChartDetails struct {
Name string
}
var chartData bytes.Buffer
_ = t.Execute(&chartData, ChartDetails{project})
if err := out.Write("Chart.yaml", chartData.Bytes()); err != nil {
return err
}
for name, o := range objects {
j, err := json.Marshal(o)
if err != nil {
return err
}
b, err := jsonToYaml(j, 2)
if err != nil {
return err
}
if err := out.Write(filepath.Join("templates", name), b); err != nil {
return err
}
}
return nil
}
type Outputer struct {
Dir string
}
func (o Outputer) Write(path string, content []byte) error {
out := filepath.Join(o.Dir, path)
os.MkdirAll(filepath.Dir(out), 0744)
return ioutil.WriteFile(out, content, 0644)
}
// Convert JSON to YAML.
func jsonToYaml(j []byte, spaces int) ([]byte, error) {
// Convert the JSON to an object.
var jsonObj interface{}
// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
// Go JSON library doesn't try to pick the right number type (int, float,
// etc.) when unmarshling to interface{}, it just picks float64
// universally. go-yaml does go through the effort of picking the right
// number type, so we can preserve number type throughout this process.
err := yaml.Unmarshal(j, &jsonObj)
if err != nil {
return nil, err
}
var b bytes.Buffer
encoder := yaml.NewEncoder(&b)
encoder.SetIndent(spaces)
if err := encoder.Encode(jsonObj); err != nil {
return nil, err
}
return b.Bytes(), nil
// Marshal this object into YAML.
// return yaml.Marshal(jsonObj)
}

72
transform/kube.go Normal file
View File

@ -0,0 +1,72 @@
package transform
import (
"fmt"
"github.com/compose-spec/compose-go/types"
"github.com/docker/helm-prototype/pkg/compose"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
core "k8s.io/api/core/v1"
apps "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/runtime"
)
func MapToKubernetesObjects(model *compose.Project) (map[string]runtime.Object, error) {
objects := map[string]runtime.Object{}
for _, service := range model.Services {
objects[fmt.Sprintf("%s-service.yaml", service.Name)] = mapToService(service)
objects[fmt.Sprintf("%s-deployment.yaml", service.Name)] = mapToDeployment(service)
for _, vol := range service.Volumes {
if vol.Type == "volume" {
objects[fmt.Sprintf("%s-persistentvolumeclain.yaml", service.Name)] = mapToPVC(service, vol)
}
}
}
return objects, nil
}
func mapToService(service types.ServiceConfig) *core.Service {
return &core.Service{
ObjectMeta: meta.ObjectMeta{
Name: service.Name,
},
Spec: core.ServiceSpec{
Selector: map[string]string{"com.docker.compose.service": service.Name},
},
}
}
func mapToDeployment(service types.ServiceConfig) *apps.Deployment {
return &apps.Deployment{
ObjectMeta: meta.ObjectMeta{
Name: service.Name,
Labels: map[string]string{"com.docker.compose.service": service.Name},
},
Spec: apps.DeploymentSpec{
Template: core.PodTemplateSpec{
ObjectMeta: meta.ObjectMeta{
Labels: map[string]string{"com.docker.compose.service": service.Name},
},
Spec: core.PodSpec{
Containers: []core.Container{
{
Name: service.Name,
Image: service.Image,
},
},
},
},
},
}
}
func mapToPVC(service types.ServiceConfig, vol types.ServiceVolumeConfig) runtime.Object {
return &core.PersistentVolumeClaim{
ObjectMeta: meta.ObjectMeta{
Name: vol.Source,
Labels: map[string]string{"com.docker.compose.service": service.Name},
},
Spec: core.PersistentVolumeClaimSpec{
VolumeName: vol.Source,
},
}
}