breaking app.go into loader and transformer, to be continue

This commit is contained in:
Tuna 2016-07-28 19:31:12 +07:00
parent 0e485c6a5f
commit 85b0b6a2b8
7 changed files with 719 additions and 456 deletions

View File

@ -27,12 +27,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/urfave/cli"
"github.com/docker/docker/api/client/bundlefile"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/docker"
"github.com/docker/libcompose/lookup"
"github.com/docker/libcompose/project"
"encoding/json"
"io/ioutil"
@ -45,6 +39,7 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions"
client "k8s.io/kubernetes/pkg/client/unversioned"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/intstr"
@ -52,8 +47,12 @@ import (
// install kubernetes api
_ "github.com/openshift/origin/pkg/deploy/api/install"
"github.com/fatih/structs"
"github.com/ghodss/yaml"
"github.com/skippbox/kompose/pkg/kobject"
"github.com/skippbox/kompose/pkg/transformer"
"github.com/docker/libcompose/lookup"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/project"
)
const (
@ -61,49 +60,7 @@ const (
DefaultComposeFile = "docker-compose.yml"
)
var unsupportedKey = map[string]int{
"Build": 0,
"CapAdd": 0,
"CapDrop": 0,
"CPUSet": 0,
"CPUShares": 0,
"CPUQuota": 0,
"CgroupParent": 0,
"Devices": 0,
"DependsOn": 0,
"DNS": 0,
"DNSSearch": 0,
"DomainName": 0,
"Entrypoint": 0,
"EnvFile": 0,
"Expose": 0,
"Extends": 0,
"ExternalLinks": 0,
"ExtraHosts": 0,
"Hostname": 0,
"Ipc": 0,
"Logging": 0,
"MacAddress": 0,
"MemLimit": 0,
"MemSwapLimit": 0,
"NetworkMode": 0,
"Networks": 0,
"Pid": 0,
"SecurityOpt": 0,
"ShmSize": 0,
"StopSignal": 0,
"VolumeDriver": 0,
"VolumesFrom": 0,
"Uts": 0,
"ReadOnly": 0,
"StdinOpen": 0,
"Tty": 0,
"User": 0,
"Ulimits": 0,
"Dockerfile": 0,
"Net": 0,
"Args": 0,
}
var inputFormat = "compose"
var composeOptions = map[string]string{
"Build": "build",
@ -315,7 +272,7 @@ func createOutFile(out string) *os.File {
}
// Init RC object
func initRC(name string, service ServiceConfig, replicas int) *api.ReplicationController {
func initRC(name string, service kobject.ServiceConfig, replicas int) *api.ReplicationController {
rc := &api.ReplicationController{
TypeMeta: unversioned.TypeMeta{
Kind: "ReplicationController",
@ -347,7 +304,7 @@ func initRC(name string, service ServiceConfig, replicas int) *api.ReplicationCo
}
// Init SC object
func initSC(name string, service ServiceConfig) *api.Service {
func initSC(name string, service kobject.ServiceConfig) *api.Service {
sc := &api.Service{
TypeMeta: unversioned.TypeMeta{
Kind: "Service",
@ -365,7 +322,7 @@ func initSC(name string, service ServiceConfig) *api.Service {
}
// Init DC object
func initDC(name string, service ServiceConfig, replicas int) *extensions.Deployment {
func initDC(name string, service kobject.ServiceConfig, replicas int) *extensions.Deployment {
dc := &extensions.Deployment{
TypeMeta: unversioned.TypeMeta{
Kind: "Deployment",
@ -400,7 +357,7 @@ func initDC(name string, service ServiceConfig, replicas int) *extensions.Deploy
}
// Init DS object
func initDS(name string, service ServiceConfig) *extensions.DaemonSet {
func initDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet {
ds := &extensions.DaemonSet{
TypeMeta: unversioned.TypeMeta{
Kind: "DaemonSet",
@ -429,7 +386,7 @@ func initDS(name string, service ServiceConfig) *extensions.DaemonSet {
}
// Init RS object
func initRS(name string, service ServiceConfig, replicas int) *extensions.ReplicaSet {
func initRS(name string, service kobject.ServiceConfig, replicas int) *extensions.ReplicaSet {
rs := &extensions.ReplicaSet{
TypeMeta: unversioned.TypeMeta{
Kind: "ReplicaSet",
@ -460,7 +417,7 @@ func initRS(name string, service ServiceConfig, replicas int) *extensions.Replic
}
// initDeploymentConfig initialize OpenShifts DeploymentConfig object
func initDeploymentConfig(name string, service ServiceConfig, replicas int) *deployapi.DeploymentConfig {
func initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int) *deployapi.DeploymentConfig {
dc := &deployapi.DeploymentConfig{
TypeMeta: unversioned.TypeMeta{
Kind: "DeploymentConfig",
@ -493,7 +450,7 @@ func initDeploymentConfig(name string, service ServiceConfig, replicas int) *dep
}
// Configure the environment variables.
func configEnvs(name string, service ServiceConfig) []api.EnvVar {
func configEnvs(name string, service kobject.ServiceConfig) []api.EnvVar {
envs := []api.EnvVar{}
for _, v := range service.Environment {
envs = append(envs, api.EnvVar{
@ -506,7 +463,7 @@ func configEnvs(name string, service ServiceConfig) []api.EnvVar {
}
// Configure the container volumes.
func configVolumes(service ServiceConfig) ([]api.VolumeMount, []api.Volume) {
func configVolumes(service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) {
volumesMount := []api.VolumeMount{}
volumes := []api.Volume{}
volumeSource := api.VolumeSource{}
@ -574,16 +531,16 @@ func isPath(substring string) bool {
}
// Configure the container ports.
func configPorts(name string, service ServiceConfig) []api.ContainerPort {
func configPorts(name string, service kobject.ServiceConfig) []api.ContainerPort {
ports := []api.ContainerPort{}
for _, port := range service.Port {
var p api.Protocol
switch port.Protocol {
default:
p = api.ProtocolTCP
case ProtocolTCP:
case kobject.ProtocolTCP:
p = api.ProtocolTCP
case ProtocolUDP:
case kobject.ProtocolUDP:
p = api.ProtocolUDP
}
ports = append(ports, api.ContainerPort{
@ -596,7 +553,7 @@ func configPorts(name string, service ServiceConfig) []api.ContainerPort {
}
// Configure the container service ports.
func configServicePorts(name string, service ServiceConfig) []api.ServicePort {
func configServicePorts(name string, service kobject.ServiceConfig) []api.ServicePort {
servicePorts := []api.ServicePort{}
for _, port := range service.Port {
if port.HostPort == 0 {
@ -606,9 +563,9 @@ func configServicePorts(name string, service ServiceConfig) []api.ServicePort {
switch port.Protocol {
default:
p = api.ProtocolTCP
case ProtocolTCP:
case kobject.ProtocolTCP:
p = api.ProtocolTCP
case ProtocolUDP:
case kobject.ProtocolUDP:
p = api.ProtocolUDP
}
var targetPort intstr.IntOrString
@ -625,7 +582,7 @@ func configServicePorts(name string, service ServiceConfig) []api.ServicePort {
}
// Transform data to json/yaml
func transformer(obj runtime.Object, generateYaml bool) ([]byte, error) {
func transformData(obj runtime.Object, generateYaml bool) ([]byte, error) {
// Convert to versioned object
objectVersion := obj.GetObjectKind().GroupVersionKind()
version := unversioned.GroupVersion{Group: objectVersion.Group, Version: objectVersion.Version}
@ -646,284 +603,6 @@ func transformer(obj runtime.Object, generateYaml bool) ([]byte, error) {
return data, nil
}
// load Environment Variable from bundles file
func loadEnvVars(service bundlefile.Service) ([]EnvVar, string) {
envs := []EnvVar{}
for _, env := range service.Env {
character := "="
if strings.Contains(env, character) {
value := env[strings.Index(env, character)+1:]
name := env[0:strings.Index(env, character)]
name = strings.TrimSpace(name)
value = strings.TrimSpace(value)
envs = append(envs, EnvVar{
Name: name,
Value: value,
})
} else {
character = ":"
if strings.Contains(env, character) {
charQuote := "'"
value := env[strings.Index(env, character)+1:]
name := env[0:strings.Index(env, character)]
name = strings.TrimSpace(name)
value = strings.TrimSpace(value)
if strings.Contains(value, charQuote) {
value = strings.Trim(value, "'")
}
envs = append(envs, EnvVar{
Name: name,
Value: value,
})
} else {
return envs, "Invalid container env " + env
}
}
}
return envs, ""
}
// load Environment Variable from compose file
func loadEnvVarsFromCompose(e map[string]string) []EnvVar {
envs := []EnvVar{}
for k, v := range e {
envs = append(envs, EnvVar{
Name: k,
Value: v,
})
}
return envs
}
// load Ports from bundles file
func loadPorts(service bundlefile.Service) ([]Ports, string) {
ports := []Ports{}
for _, port := range service.Ports {
var p Protocol
switch port.Protocol {
default:
p = ProtocolTCP
case "TCP":
p = ProtocolTCP
case "UDP":
p = ProtocolUDP
}
ports = append(ports, Ports{
HostPort: int32(port.Port),
ContainerPort: int32(port.Port),
Protocol: p,
})
}
return ports, ""
}
// Load Ports from compose file
func loadPortsFromCompose(composePorts []string) ([]Ports, string) {
ports := []Ports{}
character := ":"
for _, port := range composePorts {
p := ProtocolTCP
if strings.Contains(port, character) {
hostPort := port[0:strings.Index(port, character)]
hostPort = strings.TrimSpace(hostPort)
hostPortInt, err := strconv.Atoi(hostPort)
if err != nil {
return nil, "Invalid host port of " + port
}
containerPort := port[strings.Index(port, character)+1:]
containerPort = strings.TrimSpace(containerPort)
containerPortInt, err := strconv.Atoi(containerPort)
if err != nil {
return nil, "Invalid container port of " + port
}
ports = append(ports, Ports{
HostPort: int32(hostPortInt),
ContainerPort: int32(containerPortInt),
Protocol: p,
})
} else {
containerPortInt, err := strconv.Atoi(port)
if err != nil {
return nil, "Invalid container port of " + port
}
ports = append(ports, Ports{
ContainerPort: int32(containerPortInt),
Protocol: p,
})
}
}
return ports, ""
}
// load Image from bundles file
func loadImage(service bundlefile.Service) (string, string) {
character := "@"
if strings.Contains(service.Image, character) {
return service.Image[0:strings.Index(service.Image, character)], ""
}
return "", "Invalid image format"
}
// Load DAB file into KomposeObject
func loadBundlesFile(file string) KomposeObject {
komposeObject := KomposeObject{
ServiceConfigs: make(map[string]ServiceConfig),
}
buf, err := ioutil.ReadFile(file)
if err != nil {
logrus.Fatalf("Failed to read bundles file: %v", err)
}
reader := strings.NewReader(string(buf))
bundle, err := bundlefile.LoadFile(reader)
if err != nil {
logrus.Fatalf("Failed to parse bundles file: %v", err)
}
for name, service := range bundle.Services {
checkUnsupportedKey(service)
serviceConfig := ServiceConfig{}
serviceConfig.Command = service.Command
serviceConfig.Args = service.Args
// convert bundle labels to annotations
serviceConfig.Annotations = service.Labels
image, err := loadImage(service)
if err != "" {
logrus.Fatalf("Failed to load image from bundles file: %v", err)
}
serviceConfig.Image = image
envs, err := loadEnvVars(service)
if err != "" {
logrus.Fatalf("Failed to load envvar from bundles file: %v", err)
}
serviceConfig.Environment = envs
ports, err := loadPorts(service)
if err != "" {
logrus.Fatalf("Failed to load ports from bundles file: %v", err)
}
serviceConfig.Port = ports
if service.WorkingDir != nil {
serviceConfig.WorkingDir = *service.WorkingDir
}
komposeObject.ServiceConfigs[name] = serviceConfig
}
return komposeObject
}
// Load compose file into KomposeObject
func loadComposeFile(file string) KomposeObject {
komposeObject := KomposeObject{
ServiceConfigs: make(map[string]ServiceConfig),
}
context := &docker.Context{}
if file == "" {
file = "docker-compose.yml"
}
context.ComposeFiles = []string{file}
if context.ResourceLookup == nil {
context.ResourceLookup = &lookup.FileResourceLookup{}
}
if context.EnvironmentLookup == nil {
cwd, err := os.Getwd()
if err != nil {
return KomposeObject{}
}
context.EnvironmentLookup = &lookup.ComposableEnvLookup{
Lookups: []config.EnvironmentLookup{
&lookup.EnvfileLookup{
Path: filepath.Join(cwd, ".env"),
},
&lookup.OsEnvLookup{},
},
}
}
// load compose file into composeObject
composeObject := project.NewProject(&context.Context, nil, nil)
err := composeObject.Parse()
if err != nil {
logrus.Fatalf("Failed to load compose file: %v", err)
}
// transform composeObject into komposeObject
composeServiceNames := composeObject.ServiceConfigs.Keys()
// volume config and network config are not supported
if len(composeObject.NetworkConfigs) > 0 {
logrus.Warningf("Unsupported network configuration of compose v2 - ignoring")
}
if len(composeObject.VolumeConfigs) > 0 {
logrus.Warningf("Unsupported volume configuration of compose v2 - ignoring")
}
networksWarningFound := false
for _, name := range composeServiceNames {
if composeServiceConfig, ok := composeObject.ServiceConfigs.Get(name); ok {
//FIXME: networks always contains one default element, even it isn't declared in compose v2.
if len(composeServiceConfig.Networks.Networks) > 0 &&
composeServiceConfig.Networks.Networks[0].Name != "default" &&
!networksWarningFound {
logrus.Warningf("Unsupported key networks - ignoring")
networksWarningFound = true
}
checkUnsupportedKey(composeServiceConfig)
serviceConfig := ServiceConfig{}
serviceConfig.Image = composeServiceConfig.Image
serviceConfig.ContainerName = composeServiceConfig.ContainerName
// load environment variables
envs := loadEnvVarsFromCompose(composeServiceConfig.Environment.ToMap())
serviceConfig.Environment = envs
// load ports
ports, err := loadPortsFromCompose(composeServiceConfig.Ports)
if err != "" {
logrus.Fatalf("Failed to load ports from compose file: %v", err)
}
serviceConfig.Port = ports
serviceConfig.WorkingDir = composeServiceConfig.WorkingDir
serviceConfig.Volumes = composeServiceConfig.Volumes
// convert compose labels to annotations
serviceConfig.Annotations = map[string]string(composeServiceConfig.Labels)
serviceConfig.CPUSet = composeServiceConfig.CPUSet
serviceConfig.CPUShares = composeServiceConfig.CPUShares
serviceConfig.CPUQuota = composeServiceConfig.CPUQuota
serviceConfig.CapAdd = composeServiceConfig.CapAdd
serviceConfig.CapDrop = composeServiceConfig.CapDrop
serviceConfig.Expose = composeServiceConfig.Expose
serviceConfig.Privileged = composeServiceConfig.Privileged
serviceConfig.Restart = composeServiceConfig.Restart
serviceConfig.User = composeServiceConfig.User
komposeObject.ServiceConfigs[name] = serviceConfig
}
}
return komposeObject
}
type convertOptions struct {
toStdout bool
createD bool
createRC bool
createDS bool
createDeploymentConfig bool
createChart bool
generateYaml bool
replicas int
inputFile string
outFile string
}
// Convert komposeObject to K8S controllers
func komposeConvert(komposeObject KomposeObject, opt convertOptions) []runtime.Object {
var svcnames []string
@ -1119,28 +798,28 @@ func ConvertToVersion(objs []runtime.Object) ([]runtime.Object, error) {
return ret, nil
}
func validateFlags(opt convertOptions, singleOutput bool, dabFile, inputFile string) {
if len(opt.outFile) != 0 && opt.toStdout {
func validateFlags(opt kobject.ConvertOptions, singleOutput bool, dabFile, inputFile string) {
if len(opt.OutFile) != 0 && opt.ToStdout {
logrus.Fatalf("Error: --out and --stdout can't be set at the same time")
}
if opt.createChart && opt.toStdout {
if opt.CreateChart && opt.ToStdout {
logrus.Fatalf("Error: chart cannot be generated when --stdout is specified")
}
if opt.replicas < 0 {
if opt.Replicas < 0 {
logrus.Fatalf("Error: --replicas cannot be negative")
}
if singleOutput {
count := 0
if opt.createD {
if opt.CreateD {
count++
}
if opt.createDS {
if opt.CreateDS {
count++
}
if opt.createRC {
if opt.CreateRC {
count++
}
if opt.createDeploymentConfig {
if opt.CreateDeploymentConfig {
count++
}
if count > 1 {
@ -1172,51 +851,42 @@ func Convert(c *cli.Context) {
createD = true
}
komposeObject := kobject.KomposeObject{
ServiceConfigs: make(map[string]kobject.ServiceConfig),
}
file := inputFile
if len(dabFile) > 0 {
inputFormat = "bundle"
file = dabFile
}
opt := convertOptions{
toStdout: toStdout,
createD: createD,
createRC: createRC,
createDS: createDS,
createDeploymentConfig: createDeploymentConfig,
createChart: createChart,
generateYaml: generateYaml,
replicas: replicas,
inputFile: file,
outFile: outFile,
opt := kobject.ConvertOptions{
ToStdout: toStdout,
CreateD: createD,
CreateRC: createRC,
CreateDS: createDS,
CreateDeploymentConfig: createDeploymentConfig,
CreateChart: createChart,
GenerateYaml: generateYaml,
Replicas: replicas,
InputFile: file,
OutFile: outFile,
}
f := createOutFile(opt.OutFile)
defer f.Close()
validateFlags(opt, singleOutput, dabFile, inputFile)
komposeObject := KomposeObject{}
if len(dabFile) > 0 {
komposeObject = loadBundlesFile(dabFile)
} else {
komposeObject = loadComposeFile(inputFile)
}
komposeObject.Loader(file, inputFormat)
//komposeObject.Transformer(opt)
// Convert komposeObject to K8S controllers
objects := komposeConvert(komposeObject, opt)
mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames := transformer.Transform(komposeObject, opt)
// print output to places as needed
PrintList(objects, opt)
}
func checkUnsupportedKey(service interface{}) {
s := structs.New(service)
for _, f := range s.Fields() {
if f.IsExported() && !f.IsZero() && f.Name() != "Networks" {
if count, ok := unsupportedKey[f.Name()]; ok && count == 0 {
logrus.Warningf("Unsupported key %s - ignoring", composeOptions[f.Name()])
unsupportedKey[f.Name()]++
}
}
}
// Print output
transformer.PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames, opt, f)
}
// Either print to stdout or to file/s

View File

@ -1,70 +0,0 @@
/*
Copyright 2016 Skippbox, Ltd All rights reserved.
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 app
// KomposeObject holds the generic struct of Kompose transformation
type KomposeObject struct {
ServiceConfigs map[string]ServiceConfig
}
// ServiceConfig holds the basic struct of a container
type ServiceConfig struct {
ContainerName string
Image string
Environment []EnvVar
Port []Ports
Command []string
WorkingDir string
Args []string
Volumes []string
Network []string
Labels map[string]string
Annotations map[string]string
CPUSet string
CPUShares int64
CPUQuota int64
CapAdd []string
CapDrop []string
Entrypoint []string
Expose []string
Privileged bool
Restart string
User string
}
// EnvVar holds the environment variable struct of a container
type EnvVar struct {
Name string
Value string
}
// Ports holds the ports struct of a container
type Ports struct {
HostPort int32
ContainerPort int32
Protocol Protocol
}
// Protocol defines network protocols supported for things like container ports.
type Protocol string
const (
// ProtocolTCP is the TCP protocol.
ProtocolTCP Protocol = "TCP"
// ProtocolUDP is the UDP protocol.
ProtocolUDP Protocol = "UDP"
)

167
pkg/kobject/kobject.go Normal file
View File

@ -0,0 +1,167 @@
/*
Copyright 2016 Skippbox, Ltd All rights reserved.
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 kobject
import (
"github.com/Sirupsen/logrus"
"fmt"
"github.com/skippbox/kompose/pkg/loader"
"github.com/fatih/structs"
"github.com/skippbox/kompose/pkg/transformer"
)
var unsupportedKey = map[string]int{
"Build": 0,
"CapAdd": 0,
"CapDrop": 0,
"CPUSet": 0,
"CPUShares": 0,
"CPUQuota": 0,
"CgroupParent": 0,
"Devices": 0,
"DependsOn": 0,
"DNS": 0,
"DNSSearch": 0,
"DomainName": 0,
"Entrypoint": 0,
"EnvFile": 0,
"Expose": 0,
"Extends": 0,
"ExternalLinks": 0,
"ExtraHosts": 0,
"Hostname": 0,
"Ipc": 0,
"Logging": 0,
"MacAddress": 0,
"MemLimit": 0,
"MemSwapLimit": 0,
"NetworkMode": 0,
"Networks": 0,
"Pid": 0,
"SecurityOpt": 0,
"ShmSize": 0,
"StopSignal": 0,
"VolumeDriver": 0,
"VolumesFrom": 0,
"Uts": 0,
"ReadOnly": 0,
"StdinOpen": 0,
"Tty": 0,
"User": 0,
"Ulimits": 0,
"Dockerfile": 0,
"Net": 0,
"Args": 0,
}
// KomposeObject holds the generic struct of Kompose transformation
type KomposeObject struct {
ServiceConfigs map[string]ServiceConfig
}
type ConvertOptions struct {
ToStdout bool
CreateD bool
CreateRC bool
CreateDS bool
CreateDeploymentConfig bool
CreateChart bool
GenerateYaml bool
Replicas int
InputFile string
OutFile string
}
// ServiceConfig holds the basic struct of a container
type ServiceConfig struct {
ContainerName string
Image string
Environment []EnvVar
Port []Ports
Command []string
WorkingDir string
Args []string
Volumes []string
Network []string
Labels map[string]string
CPUSet string
CPUShares int64
CPUQuota int64
CapAdd []string
CapDrop []string
Entrypoint []string
Expose []string
Privileged bool
Restart string
User string
}
// EnvVar holds the environment variable struct of a container
type EnvVar struct {
Name string
Value string
}
// Ports holds the ports struct of a container
type Ports struct {
HostPort int32
ContainerPort int32
Protocol Protocol
}
// Protocol defines network protocols supported for things like container ports.
type Protocol string
const (
// ProtocolTCP is the TCP protocol.
ProtocolTCP Protocol = "TCP"
// ProtocolUDP is the UDP protocol.
ProtocolUDP Protocol = "UDP"
)
// loader takes input and converts to KomposeObject
func (k *KomposeObject) Loader(file string, inp string) {
switch inp {
case "bundle":
//k.loadBundleFile(file)
loader.LoadBundle(k, file)
case "compose":
//k.loadComposeFile(file)
loader.LoadCompose(k, file)
default:
logrus.Fatalf("Input file format is not supported")
}
}
// transformer takes KomposeObject and converts to K8S / OpenShift primitives
func (k *KomposeObject) Transformer(opt ConvertOptions) {
transformer.Transform(k, opt)
}
func CheckUnsupportedKey(service interface{}) {
s := structs.New(service)
for _, f := range s.Fields() {
if f.IsExported() && !f.IsZero() {
if count, ok := unsupportedKey[f.Name()]; ok && count == 0 {
fmt.Println("WARNING: Unsupported key " + f.Name() + " - ignoring")
unsupportedKey[f.Name()]++
}
}
}
}

View File

@ -1 +1,140 @@
/*
Copyright 2016 Skippbox, Ltd All rights reserved.
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 loader
import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/client/bundlefile"
"io/ioutil"
"strings"
"github.com/skippbox/kompose/pkg/kobject"
)
// load Image from bundles file
func loadImage(service bundlefile.Service) (string, string) {
character := "@"
if strings.Contains(service.Image, character) {
return service.Image[0:strings.Index(service.Image, character)], ""
}
return "", "Invalid image format"
}
// load Environment Variable from bundles file
func loadEnvVarsfromBundle(service bundlefile.Service) ([]kobject.EnvVar, string) {
envs := []kobject.EnvVar{}
for _, env := range service.Env {
character := "="
if strings.Contains(env, character) {
value := env[strings.Index(env, character)+1:]
name := env[0:strings.Index(env, character)]
name = strings.TrimSpace(name)
value = strings.TrimSpace(value)
envs = append(envs, kobject.EnvVar{
Name: name,
Value: value,
})
} else {
character = ":"
if strings.Contains(env, character) {
charQuote := "'"
value := env[strings.Index(env, character)+1:]
name := env[0:strings.Index(env, character)]
name = strings.TrimSpace(name)
value = strings.TrimSpace(value)
if strings.Contains(value, charQuote) {
value = strings.Trim(value, "'")
}
envs = append(envs, kobject.EnvVar{
Name: name,
Value: value,
})
} else {
return envs, "Invalid container env " + env
}
}
}
return envs, ""
}
// load Ports from bundles file
func loadPortsfromBundle(service bundlefile.Service) ([]kobject.Ports, string) {
ports := []kobject.Ports{}
for _, port := range service.Ports {
var p kobject.Protocol
switch port.Protocol {
default:
p = kobject.ProtocolTCP
case "TCP":
p = kobject.ProtocolTCP
case "UDP":
p = kobject.ProtocolUDP
}
ports = append(ports, kobject.Ports{
HostPort: int32(port.Port),
ContainerPort: int32(port.Port),
Protocol: p,
})
}
return ports, ""
}
// load Bundlefile into KomposeObject
func LoadBundle(komposeObject *kobject.KomposeObject, file string) {
buf, err := ioutil.ReadFile(file)
if err != nil {
logrus.Fatalf("Failed to read bundles file: ", err)
}
reader := strings.NewReader(string(buf))
bundle, err := bundlefile.LoadFile(reader)
if err != nil {
logrus.Fatalf("Failed to parse bundles file: ", err)
}
for name, service := range bundle.Services {
kobject.CheckUnsupportedKey(service)
serviceConfig := kobject.ServiceConfig{}
serviceConfig.Command = service.Command
serviceConfig.Args = service.Args
// convert bundle labels to annotations
serviceConfig.Annotations = service.Labels
image, err := loadImage(service)
if err != "" {
logrus.Fatalf("Failed to load image from bundles file: " + err)
}
serviceConfig.Image = image
envs, err := loadEnvVarsfromBundle(service)
if err != "" {
logrus.Fatalf("Failed to load envvar from bundles file: " + err)
}
serviceConfig.Environment = envs
ports, err := loadPortsfromBundle(service)
if err != "" {
logrus.Fatalf("Failed to load ports from bundles file: " + err)
}
serviceConfig.Port = ports
if service.WorkingDir != nil {
serviceConfig.WorkingDir = *service.WorkingDir
}
komposeObject.ServiceConfigs[name] = serviceConfig
}
}

View File

@ -1 +1,170 @@
/*
Copyright 2016 Skippbox, Ltd All rights reserved.
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 loader
import (
"github.com/Sirupsen/logrus"
"github.com/docker/libcompose/docker"
"github.com/docker/libcompose/project"
"github.com/skippbox/kompose/pkg/kobject"
"strconv"
"strings"
)
// load Environment Variable from compose file
func loadEnvVarsfromCompose(e map[string]string) []kobject.EnvVar {
envs := []kobject.EnvVar{}
for k, v := range e {
envs = append(envs, kobject.EnvVar{
Name: k,
Value: v,
})
}
return envs
}
// Load Ports from compose file
func loadPortsFromCompose(composePorts []string) ([]kobject.Ports, string) {
ports := []kobject.Ports{}
character := ":"
for _, port := range composePorts {
p := kobject.ProtocolTCP
if strings.Contains(port, character) {
hostPort := port[0:strings.Index(port, character)]
hostPort = strings.TrimSpace(hostPort)
hostPortInt, err := strconv.Atoi(hostPort)
if err != nil {
return nil, "Invalid host port of " + port
}
containerPort := port[strings.Index(port, character)+1:]
containerPort = strings.TrimSpace(containerPort)
containerPortInt, err := strconv.Atoi(containerPort)
if err != nil {
return nil, "Invalid container port of " + port
}
ports = append(ports, kobject.Ports{
HostPort: int32(hostPortInt),
ContainerPort: int32(containerPortInt),
Protocol: p,
})
} else {
containerPortInt, err := strconv.Atoi(port)
if err != nil {
return nil, "Invalid container port of " + port
}
ports = append(ports, kobject.Ports{
ContainerPort: int32(containerPortInt),
Protocol: p,
})
}
}
return ports, ""
}
// load Docker Compose file into KomposeObject
func LoadCompose(komposeObject *kobject.KomposeObject, file string) {
context := &docker.Context{}
if file == "" {
file = "docker-compose.yml"
}
context.ComposeFiles = []string{file}
if context.ResourceLookup == nil {
context.ResourceLookup = &lookup.FileResourceLookup{}
}
if context.EnvironmentLookup == nil {
cwd, err := os.Getwd()
if err != nil {
return KomposeObject{}
}
context.EnvironmentLookup = &lookup.ComposableEnvLookup{
Lookups: []config.EnvironmentLookup{
&lookup.EnvfileLookup{
Path: filepath.Join(cwd, ".env"),
},
&lookup.OsEnvLookup{},
},
}
}
// load compose file into composeObject
composeObject := project.NewProject(&context.Context, nil, nil)
err := composeObject.Parse()
if err != nil {
logrus.Fatalf("Failed to load compose file", err)
}
// transform composeObject into komposeObject
composeServiceNames := composeObject.ServiceConfigs.Keys()
// volume config and network config are not supported
if len(composeObject.NetworkConfigs) > 0 {
logrus.Warningf("Unsupported network configuration of compose v2 - ignoring")
}
if len(composeObject.VolumeConfigs) > 0 {
logrus.Warningf("Unsupported volume configuration of compose v2 - ignoring")
}
networksWarningFound := false
for _, name := range composeServiceNames {
if composeServiceConfig, ok := composeObject.ServiceConfigs.Get(name); ok {
//FIXME: networks always contains one default element, even it isn't declared in compose v2.
if len(composeServiceConfig.Networks.Networks) > 0 &&
composeServiceConfig.Networks.Networks[0].Name != "default" &&
!networksWarningFound {
logrus.Warningf("Unsupported key networks - ignoring")
networksWarningFound = true
}
kobject.CheckUnsupportedKey(composeServiceConfig)
serviceConfig := kobject.ServiceConfig{}
serviceConfig.Image = composeServiceConfig.Image
serviceConfig.ContainerName = composeServiceConfig.ContainerName
// load environment variables
envs := loadEnvVarsfromCompose(composeServiceConfig.Environment.ToMap())
serviceConfig.Environment = envs
// load ports
ports, err := loadPortsFromCompose(composeServiceConfig.Ports)
if err != "" {
logrus.Fatalf("Failed to load ports from compose file: " + err)
}
serviceConfig.Port = ports
serviceConfig.WorkingDir = composeServiceConfig.WorkingDir
serviceConfig.Volumes = composeServiceConfig.Volumes
// convert compose labels to annotations
serviceConfig.Annotations = map[string]string(composeServiceConfig.Labels)
serviceConfig.CPUSet = composeServiceConfig.CPUSet
serviceConfig.CPUShares = composeServiceConfig.CPUShares
serviceConfig.CPUQuota = composeServiceConfig.CPUQuota
serviceConfig.CapAdd = composeServiceConfig.CapAdd
serviceConfig.CapDrop = composeServiceConfig.CapDrop
serviceConfig.Expose = composeServiceConfig.Expose
serviceConfig.Privileged = composeServiceConfig.Privileged
serviceConfig.Restart = composeServiceConfig.Restart
serviceConfig.User = composeServiceConfig.User
komposeObject.ServiceConfigs[name] = serviceConfig
}
}
}

View File

@ -1 +1,189 @@
package transformer
import (
"github.com/skippbox/kompose/pkg/kobject"
"fmt"
"os"
)
func Transform(komposeObject *kobject.KomposeObject, opt kobject.ConvertOptions) (map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, []string) {
mServices := make(map[string][]byte)
mReplicationControllers := make(map[string][]byte)
mDeployments := make(map[string][]byte)
mDaemonSets := make(map[string][]byte)
mReplicaSets := make(map[string][]byte)
// OpenShift DeploymentConfigs
mDeploymentConfigs := make(map[string][]byte)
f := createOutFile(opt.outFile)
defer f.Close()
var svcnames []string
for name, service := range komposeObject.ServiceConfigs {
svcnames = append(svcnames, name)
rc := initRC(name, service, opt.replicas)
sc := initSC(name, service)
dc := initDC(name, service, opt.replicas)
ds := initDS(name, service)
osDC := initDeploymentConfig(name, service, opt.replicas) // OpenShift DeploymentConfigs
// Configure the environment variables.
envs := configEnvs(name, service)
// Configure the container command.
var cmds []string
for _, cmd := range service.Command {
cmds = append(cmds, cmd)
}
// Configure the container volumes.
volumesMount, volumes := configVolumes(service)
// Configure the container ports.
ports := configPorts(name, service)
// Configure the service ports.
servicePorts := configServicePorts(name, service)
sc.Spec.Ports = servicePorts
// Configure label
labels := map[string]string{"service": name}
sc.ObjectMeta.Labels = labels
// Configure annotations
annotations := map[string]string{}
for key, value := range service.Annotations {
annotations[key] = value
}
sc.ObjectMeta.Annotations = annotations
// fillTemplate fills the pod template with the value calculated from config
fillTemplate := func(template *api.PodTemplateSpec) {
template.Spec.Containers[0].Env = envs
template.Spec.Containers[0].Command = cmds
template.Spec.Containers[0].WorkingDir = service.WorkingDir
template.Spec.Containers[0].VolumeMounts = volumesMount
template.Spec.Volumes = volumes
// Configure the container privileged mode
if service.Privileged == true {
template.Spec.Containers[0].SecurityContext = &api.SecurityContext{
Privileged: &service.Privileged,
}
}
template.Spec.Containers[0].Ports = ports
template.ObjectMeta.Labels = labels
// Configure the container restart policy.
switch service.Restart {
case "", "always":
template.Spec.RestartPolicy = api.RestartPolicyAlways
case "no":
template.Spec.RestartPolicy = api.RestartPolicyNever
case "on-failure":
template.Spec.RestartPolicy = api.RestartPolicyOnFailure
default:
logrus.Fatalf("Unknown restart policy %s for service %s", service.Restart, name)
}
}
// fillObjectMeta fills the metadata with the value calculated from config
fillObjectMeta := func(meta *api.ObjectMeta) {
meta.Labels = labels
meta.Annotations = annotations
}
// Update each supported controllers
updateController(rc, fillTemplate, fillObjectMeta)
updateController(dc, fillTemplate, fillObjectMeta)
updateController(ds, fillTemplate, fillObjectMeta)
// OpenShift DeploymentConfigs
updateController(osDC, fillTemplate, fillObjectMeta)
// convert datarc to json / yaml
datarc, err := transformer(rc, opt.generateYaml)
if err != nil {
logrus.Fatalf(err.Error())
}
// convert datadc to json / yaml
datadc, err := transformer(dc, opt.generateYaml)
if err != nil {
logrus.Fatalf(err.Error())
}
// convert datads to json / yaml
datads, err := transformer(ds, opt.generateYaml)
if err != nil {
logrus.Fatalf(err.Error())
}
var datasvc []byte
// If ports not provided in configuration we will not make service
if len(ports) == 0 {
logrus.Warningf("[%s] Service cannot be created because of missing port.", name)
} else if len(ports) != 0 {
// convert datasvc to json / yaml
datasvc, err = transformer(sc, opt.generateYaml)
if err != nil {
logrus.Fatalf(err.Error())
}
}
// convert OpenShift DeploymentConfig to json / yaml
dataDeploymentConfig, err := transformer(osDC, opt.generateYaml)
if err != nil {
logrus.Fatalf(err.Error())
}
mServices[name] = datasvc
mReplicationControllers[name] = datarc
mDeployments[name] = datadc
mDaemonSets[name] = datads
mDeploymentConfigs[name] = dataDeploymentConfig
}
return mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames
}
func PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs map[string][]byte, svcnames []string, opt convertOptions, f *os.File) {
for k, v := range mServices {
if v != nil {
print(k, "svc", v, opt.toStdout, opt.generateYaml, f)
}
}
// If --out or --stdout is set, the validation should already prevent multiple controllers being generated
if opt.createD {
for k, v := range mDeployments {
print(k, "deployment", v, opt.toStdout, opt.generateYaml, f)
}
}
if opt.createDS {
for k, v := range mDaemonSets {
print(k, "daemonset", v, opt.toStdout, opt.generateYaml, f)
}
}
if opt.createRC {
for k, v := range mReplicationControllers {
print(k, "rc", v, opt.toStdout, opt.generateYaml, f)
}
}
if f != nil {
fmt.Fprintf(os.Stdout, "file %q created\n", opt.outFile)
}
if opt.createChart {
err := generateHelm(opt.inputFile, svcnames, opt.generateYaml, opt.createD, opt.createDS, opt.createRC, opt.outFile)
if err != nil {
logrus.Fatalf("Failed to create Chart data: %v", err)
}
}
if opt.createDeploymentConfig {
for k, v := range mDeploymentConfigs {
print(k, "deploymentconfig", v, opt.toStdout, opt.generateYaml, f)
}
}
}

View File

@ -17,9 +17,9 @@ limitations under the License.
package version
var (
// VERSION should be updated by hand at each release
// VERSION should be updated by hand at each release
VERSION = "0.0.1-beta"
// GITCOMMIT will be overwritten automatically by the build system
// GITCOMMIT will be overwritten automatically by the build system
GITCOMMIT = "HEAD"
)