Merge pull request #91 from skippbox/loader-transfomer

loader-transformer
This commit is contained in:
Tuna 2016-08-12 13:08:30 +07:00 committed by GitHub
commit a5ce678b97
14 changed files with 1577 additions and 1245 deletions

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@ package app
import (
"fmt"
"testing"
"github.com/skippbox/kompose/pkg/transformer"
)
func TestParseVolume(t *testing.T) {
@ -99,7 +100,7 @@ func TestParseVolume(t *testing.T) {
}
for _, test := range tests {
name, host, container, mode, err := parseVolume(test.volume)
name, host, container, mode, err := transformer.ParseVolume(test.volume)
if err != nil {
t.Errorf("In test case %q, returned unexpected error %v", test.test, err)
}

View File

@ -1,110 +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
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"text/template"
"github.com/Sirupsen/logrus"
)
/**
* Generate Helm Chart configuration
*/
func generateHelm(filename string, outFiles []string) error {
type ChartDetails struct {
Name string
}
extension := filepath.Ext(filename)
dirName := filename[0 : len(filename)-len(extension)]
details := ChartDetails{dirName}
manifestDir := dirName + string(os.PathSeparator) + "templates"
dir, err := os.Open(dirName)
/* Setup the initial directories/files */
if err == nil {
_ = dir.Close()
}
if err != nil {
err = os.Mkdir(dirName, 0755)
if err != nil {
return err
}
err = os.Mkdir(manifestDir, 0755)
if err != nil {
return err
}
/* Create the readme file */
readme := "This chart was created by Kompose\n"
err = ioutil.WriteFile(dirName+string(os.PathSeparator)+"README.md", []byte(readme), 0644)
if err != nil {
return err
}
/* Create the Chart.yaml file */
chart := `name: {{.Name}}
description: A generated Helm Chart for {{.Name}} from Skippbox Kompose
version: 0.0.1
keywords:
- {{.Name}}
sources:
home:
`
t, err := template.New("ChartTmpl").Parse(chart)
if err != nil {
logrus.Fatalf("Failed to generate Chart.yaml template: %s\n", err)
}
var chartData bytes.Buffer
_ = t.Execute(&chartData, details)
err = ioutil.WriteFile(dirName+string(os.PathSeparator)+"Chart.yaml", chartData.Bytes(), 0644)
if err != nil {
return err
}
}
/* Copy all related json/yaml files into the newly created manifests directory */
for _, filename := range outFiles {
if err = cpFileToChart(manifestDir, filename); err != nil {
logrus.Warningln(err)
}
if err = os.Remove(filename); err != nil {
logrus.Warningln(err)
}
}
logrus.Infof("chart created in %q\n", "."+string(os.PathSeparator)+dirName+string(os.PathSeparator))
return nil
}
func cpFileToChart(manifestDir, filename string) error {
infile, err := ioutil.ReadFile(filename)
if err != nil {
logrus.Warningf("Error reading %s: %s\n", filename, err)
return err
}
return ioutil.WriteFile(manifestDir+string(os.PathSeparator)+filename, infile, 0644)
}

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"
)

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

@ -0,0 +1,188 @@
/*
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"
"github.com/fatih/structs"
)
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 composeOptions = map[string]string{
"Build": "build",
"CapAdd": "cap_add",
"CapDrop": "cap_drop",
"CPUSet": "cpuset",
"CPUShares": "cpu_shares",
"CPUQuota": "cpu_quota",
"CgroupParent": "cgroup_parent",
"Devices": "devices",
"DependsOn": "depends_on",
"DNS": "dns",
"DNSSearch": "dns_search",
"DomainName": "domainname",
"Entrypoint": "entrypoint",
"EnvFile": "env_file",
"Expose": "expose",
"Extends": "extends",
"ExternalLinks": "external_links",
"ExtraHosts": "extra_hosts",
"Hostname": "hostname",
"Ipc": "ipc",
"Logging": "logging",
"MacAddress": "mac_address",
"MemLimit": "mem_limit",
"MemSwapLimit": "memswap_limit",
"NetworkMode": "network_mode",
"Networks": "networks",
"Pid": "pid",
"SecurityOpt": "security_opt",
"ShmSize": "shm_size",
"StopSignal": "stop_signal",
"VolumeDriver": "volume_driver",
"VolumesFrom": "volumes_from",
"Uts": "uts",
"ReadOnly": "read_only",
"StdinOpen": "stdin_open",
"Tty": "tty",
"User": "user",
"Ulimits": "ulimits",
"Dockerfile": "dockerfile",
"Net": "net",
"Args": "args",
}
// 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
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"
)
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()]++
}
}
}
}

150
pkg/loader/bundle/bundle.go Normal file
View File

@ -0,0 +1,150 @@
/*
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 bundle
import (
"io/ioutil"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/client/bundlefile"
"github.com/skippbox/kompose/pkg/kobject"
)
type Bundle struct {
}
// 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 (b *Bundle) LoadFile(file string) kobject.KomposeObject {
komposeObject := kobject.KomposeObject{
ServiceConfigs: make(map[string]kobject.ServiceConfig),
}
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
}
return komposeObject
}

View File

@ -0,0 +1,183 @@
/*
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 compose
import (
"os"
"path/filepath"
"strconv"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/libcompose/config"
"github.com/docker/libcompose/docker"
"github.com/docker/libcompose/lookup"
"github.com/docker/libcompose/project"
"github.com/skippbox/kompose/pkg/kobject"
)
type Compose struct {
}
// 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 (c *Compose) LoadFile(file string) kobject.KomposeObject {
komposeObject := kobject.KomposeObject{
ServiceConfigs: make(map[string]kobject.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 kobject.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 composeServiceConfig.Networks != nil && 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
}
}
return komposeObject
}

23
pkg/loader/loader.go Normal file
View File

@ -0,0 +1,23 @@
/*
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/skippbox/kompose/pkg/kobject"
type Loader interface {
LoadFile(file string) kobject.KomposeObject
}

View File

@ -0,0 +1,215 @@
/*
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 kubernetes
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"text/template"
"github.com/Sirupsen/logrus"
"github.com/ghodss/yaml"
"github.com/skippbox/kompose/pkg/kobject"
"github.com/skippbox/kompose/pkg/transformer"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/runtime"
deployapi "github.com/openshift/origin/pkg/deploy/api"
)
/**
* Generate Helm Chart configuration
*/
func generateHelm(filename string, outFiles []string) error {
type ChartDetails struct {
Name string
}
extension := filepath.Ext(filename)
dirName := filename[0 : len(filename)-len(extension)]
details := ChartDetails{dirName}
manifestDir := dirName + string(os.PathSeparator) + "templates"
dir, err := os.Open(dirName)
/* Setup the initial directories/files */
if err == nil {
_ = dir.Close()
}
if err != nil {
err = os.Mkdir(dirName, 0755)
if err != nil {
return err
}
err = os.Mkdir(manifestDir, 0755)
if err != nil {
return err
}
/* Create the readme file */
readme := "This chart was created by Kompose\n"
err = ioutil.WriteFile(dirName+string(os.PathSeparator)+"README.md", []byte(readme), 0644)
if err != nil {
return err
}
/* Create the Chart.yaml file */
chart := `name: {{.Name}}
description: A generated Helm Chart for {{.Name}} from Skippbox Kompose
version: 0.0.1
keywords:
- {{.Name}}
sources:
home:
`
t, err := template.New("ChartTmpl").Parse(chart)
if err != nil {
logrus.Fatalf("Failed to generate Chart.yaml template: %s\n", err)
}
var chartData bytes.Buffer
_ = t.Execute(&chartData, details)
err = ioutil.WriteFile(dirName+string(os.PathSeparator)+"Chart.yaml", chartData.Bytes(), 0644)
if err != nil {
return err
}
}
/* Copy all related json/yaml files into the newly created manifests directory */
for _, filename := range outFiles {
if err = cpFileToChart(manifestDir, filename); err != nil {
logrus.Warningln(err)
}
if err = os.Remove(filename); err != nil {
logrus.Warningln(err)
}
}
logrus.Infof("chart created in %q\n", "."+string(os.PathSeparator)+dirName+string(os.PathSeparator))
return nil
}
func cpFileToChart(manifestDir, filename string) error {
infile, err := ioutil.ReadFile(filename)
if err != nil {
logrus.Warningf("Error reading %s: %s\n", filename, err)
return err
}
return ioutil.WriteFile(manifestDir+string(os.PathSeparator)+filename, infile, 0644)
}
// PrintList will take the data converted and decide on the commandline attributes given
func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
f := transformer.CreateOutFile(opt.OutFile)
defer f.Close()
var err error
var files []string
// if asked to print to stdout or to put in single file
// we will create a list
if opt.ToStdout || f != nil {
list := &api.List{}
list.Items = objects
// version each object in the list
list.Items, err = convertToVersion(list.Items)
if err != nil {
return err
}
// version list itself
listVersion := unversioned.GroupVersion{Group: "", Version: "v1"}
convertedList, err := api.Scheme.ConvertToVersion(list, listVersion)
if err != nil {
return err
}
data, err := marshal(convertedList, opt.GenerateYaml)
if err != nil {
return fmt.Errorf("Error in marshalling the List: %v", err)
}
files = append(files, transformer.Print("", "", data, opt.ToStdout, opt.GenerateYaml, f))
} else {
var file string
// create a separate file for each provider
for _, v := range objects {
data, err := marshal(v, opt.GenerateYaml)
if err != nil {
return err
}
switch t := v.(type) {
case *api.ReplicationController:
file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f)
case *extensions.Deployment:
file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f)
case *extensions.DaemonSet:
file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f)
case *deployapi.DeploymentConfig:
file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f)
case *api.Service:
file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f)
}
files = append(files, file)
}
}
if opt.CreateChart {
generateHelm(opt.InputFile, files)
}
return nil
}
// marshal object runtime.Object and return byte array
func marshal(obj runtime.Object, yamlFormat bool) (data []byte, err error) {
// convert data to yaml or json
if yamlFormat {
data, err = yaml.Marshal(obj)
} else {
data, err = json.MarshalIndent(obj, "", " ")
}
if err != nil {
data = nil
}
return
}
// Convert all objects in objs to versioned objects
func convertToVersion(objs []runtime.Object) ([]runtime.Object, error) {
ret := []runtime.Object{}
for _, obj := range objs {
objectVersion := obj.GetObjectKind().GroupVersionKind()
version := unversioned.GroupVersion{Group: objectVersion.Group, Version: objectVersion.Version}
convertedObject, err := api.Scheme.ConvertToVersion(obj, version)
if err != nil {
return nil, err
}
ret = append(ret, convertedObject)
}
return ret, nil
}

View File

@ -0,0 +1,385 @@
/*
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 kubernetes
import (
"fmt"
"strconv"
"github.com/Sirupsen/logrus"
deployapi "github.com/openshift/origin/pkg/deploy/api"
"github.com/skippbox/kompose/pkg/kobject"
"github.com/skippbox/kompose/pkg/transformer"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/intstr"
)
type Kubernetes struct {
}
// Init RC object
func InitRC(name string, service kobject.ServiceConfig, replicas int) *api.ReplicationController {
rc := &api.ReplicationController{
TypeMeta: unversioned.TypeMeta{
Kind: "ReplicationController",
APIVersion: "v1",
},
ObjectMeta: api.ObjectMeta{
Name: name,
//Labels: map[string]string{"service": name},
},
Spec: api.ReplicationControllerSpec{
Selector: map[string]string{"service": name},
Replicas: int32(replicas),
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
//Labels: map[string]string{"service": name},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: name,
Image: service.Image,
},
},
},
},
},
}
return rc
}
// Init SC object
func InitSC(name string, service kobject.ServiceConfig) *api.Service {
sc := &api.Service{
TypeMeta: unversioned.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: api.ObjectMeta{
Name: name,
//Labels: map[string]string{"service": name},
},
Spec: api.ServiceSpec{
Selector: map[string]string{"service": name},
},
}
return sc
}
// Init DC object
func InitDC(name string, service kobject.ServiceConfig, replicas int) *extensions.Deployment {
dc := &extensions.Deployment{
TypeMeta: unversioned.TypeMeta{
Kind: "Deployment",
APIVersion: "extensions/v1beta1",
},
ObjectMeta: api.ObjectMeta{
Name: name,
Labels: map[string]string{"service": name},
},
Spec: extensions.DeploymentSpec{
Replicas: int32(replicas),
Selector: &unversioned.LabelSelector{
MatchLabels: map[string]string{"service": name},
},
//UniqueLabelKey: p.Name,
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"service": name},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: name,
Image: service.Image,
},
},
},
},
},
}
return dc
}
// Init DS object
func InitDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet {
ds := &extensions.DaemonSet{
TypeMeta: unversioned.TypeMeta{
Kind: "DaemonSet",
APIVersion: "extensions/v1beta1",
},
ObjectMeta: api.ObjectMeta{
Name: name,
},
Spec: extensions.DaemonSetSpec{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Name: name,
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: name,
Image: service.Image,
},
},
},
},
},
}
return ds
}
// Configure the container ports.
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 kobject.ProtocolTCP:
p = api.ProtocolTCP
case kobject.ProtocolUDP:
p = api.ProtocolUDP
}
ports = append(ports, api.ContainerPort{
ContainerPort: port.ContainerPort,
Protocol: p,
})
}
return ports
}
// Configure the container service ports.
func ConfigServicePorts(name string, service kobject.ServiceConfig) []api.ServicePort {
servicePorts := []api.ServicePort{}
for _, port := range service.Port {
if port.HostPort == 0 {
port.HostPort = port.ContainerPort
}
var p api.Protocol
switch port.Protocol {
default:
p = api.ProtocolTCP
case kobject.ProtocolTCP:
p = api.ProtocolTCP
case kobject.ProtocolUDP:
p = api.ProtocolUDP
}
var targetPort intstr.IntOrString
targetPort.IntVal = port.ContainerPort
targetPort.StrVal = strconv.Itoa(int(port.ContainerPort))
servicePorts = append(servicePorts, api.ServicePort{
Name: strconv.Itoa(int(port.HostPort)),
Protocol: p,
Port: port.HostPort,
TargetPort: targetPort,
})
}
return servicePorts
}
// Configure the container volumes.
func ConfigVolumes(service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) {
volumesMount := []api.VolumeMount{}
volumes := []api.Volume{}
volumeSource := api.VolumeSource{}
for _, volume := range service.Volumes {
name, host, container, mode, err := transformer.ParseVolume(volume)
if err != nil {
logrus.Warningf("Failed to configure container volume: %v", err)
continue
}
// if volume name isn't specified, set it to a random string of 20 chars
if len(name) == 0 {
name = transformer.RandStringBytes(20)
}
// check if ro/rw mode is defined, default rw
readonly := len(mode) > 0 && mode == "ro"
volumesMount = append(volumesMount, api.VolumeMount{Name: name, ReadOnly: readonly, MountPath: container})
if len(host) > 0 {
volumeSource = api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: host}}
} else {
volumeSource = api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}
}
volumes = append(volumes, api.Volume{Name: name, VolumeSource: volumeSource})
}
return volumesMount, volumes
}
// Configure the environment variables.
func ConfigEnvs(name string, service kobject.ServiceConfig) []api.EnvVar {
envs := []api.EnvVar{}
for _, v := range service.Environment {
envs = append(envs, api.EnvVar{
Name: v.Name,
Value: v.Value,
})
}
return envs
}
func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.ConvertOptions) []runtime.Object {
var svcnames []string
// this will hold all the converted data
var allobjects []runtime.Object
for name, service := range komposeObject.ServiceConfigs {
var objects []runtime.Object
svcnames = append(svcnames, name)
sc := InitSC(name, service)
if opt.CreateD {
objects = append(objects, InitDC(name, service, opt.Replicas))
}
if opt.CreateDS {
objects = append(objects, InitDS(name, service))
}
if opt.CreateRC {
objects = append(objects, InitRC(name, service, opt.Replicas))
}
// Configure the environment variables.
envs := ConfigEnvs(name, service)
// Configure the container command.
cmds := transformer.ConfigCommands(service)
// 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 := transformer.ConfigLabels(name)
sc.ObjectMeta.Labels = labels
// Configure annotations
annotations := transformer.ConfigAnnotations(service)
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 supported controller
for _, obj := range objects {
UpdateController(obj, fillTemplate, fillObjectMeta)
}
// 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 {
objects = append(objects, sc)
}
allobjects = append(allobjects, objects...)
}
return allobjects
}
// updateController updates the given object with the given pod template update function and ObjectMeta update function
func UpdateController(obj runtime.Object, updateTemplate func(*api.PodTemplateSpec), updateMeta func(meta *api.ObjectMeta)) {
switch t := obj.(type) {
case *api.ReplicationController:
if t.Spec.Template == nil {
t.Spec.Template = &api.PodTemplateSpec{}
}
updateTemplate(t.Spec.Template)
updateMeta(&t.ObjectMeta)
case *extensions.Deployment:
updateTemplate(&t.Spec.Template)
updateMeta(&t.ObjectMeta)
case *extensions.DaemonSet:
updateTemplate(&t.Spec.Template)
updateMeta(&t.ObjectMeta)
case *deployapi.DeploymentConfig:
updateTemplate(t.Spec.Template)
updateMeta(&t.ObjectMeta)
}
}
func CreateObjects(client *client.Client, objects []runtime.Object) {
for _, v := range objects {
switch t := v.(type) {
case *extensions.Deployment:
_, err := client.Deployments(api.NamespaceDefault).Create(t)
if err != nil {
logrus.Fatalf("Error: '%v' while creating deployment: %s", err, t.Name)
}
logrus.Infof("Successfully created deployment: %s", t.Name)
case *api.Service:
_, err := client.Services(api.NamespaceDefault).Create(t)
if err != nil {
logrus.Fatalf("Error: '%v' while creating service: %s", err, t.Name)
}
logrus.Infof("Successfully created service: %s", t.Name)
}
}
fmt.Println("\nApplication has been deployed to Kubernetes. You can run 'kubectl get deployment,svc' for details.")
}

View File

@ -0,0 +1,165 @@
/*
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 openshift
import (
"github.com/Sirupsen/logrus"
deployapi "github.com/openshift/origin/pkg/deploy/api"
"github.com/skippbox/kompose/pkg/kobject"
"github.com/skippbox/kompose/pkg/transformer"
"github.com/skippbox/kompose/pkg/transformer/kubernetes"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
)
type OpenShift struct {
}
// initDeploymentConfig initialize OpenShifts DeploymentConfig object
func initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int) *deployapi.DeploymentConfig {
dc := &deployapi.DeploymentConfig{
TypeMeta: unversioned.TypeMeta{
Kind: "DeploymentConfig",
APIVersion: "v1",
},
ObjectMeta: api.ObjectMeta{
Name: name,
Labels: map[string]string{"service": name},
},
Spec: deployapi.DeploymentConfigSpec{
Replicas: int32(replicas),
Selector: map[string]string{"service": name},
//UniqueLabelKey: p.Name,
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"service": name},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: name,
Image: service.Image,
},
},
},
},
},
}
return dc
}
func (k *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.ConvertOptions) []runtime.Object {
var svcnames []string
// this will hold all the converted data
var allobjects []runtime.Object
for name, service := range komposeObject.ServiceConfigs {
var objects []runtime.Object
svcnames = append(svcnames, name)
sc := kubernetes.InitSC(name, service)
if opt.CreateD {
objects = append(objects, kubernetes.InitDC(name, service, opt.Replicas))
}
if opt.CreateDS {
objects = append(objects, kubernetes.InitDS(name, service))
}
if opt.CreateRC {
objects = append(objects, kubernetes.InitRC(name, service, opt.Replicas))
}
if opt.CreateDeploymentConfig {
objects = append(objects, initDeploymentConfig(name, service, opt.Replicas)) // OpenShift DeploymentConfigs
}
// Configure the environment variables.
envs := kubernetes.ConfigEnvs(name, service)
// Configure the container command.
cmds := transformer.ConfigCommands(service)
// Configure the container volumes.
volumesMount, volumes := kubernetes.ConfigVolumes(service)
// Configure the container ports.
ports := kubernetes.ConfigPorts(name, service)
// Configure the service ports.
servicePorts := kubernetes.ConfigServicePorts(name, service)
sc.Spec.Ports = servicePorts
// Configure label
labels := transformer.ConfigLabels(name)
sc.ObjectMeta.Labels = labels
// Configure annotations
annotations := transformer.ConfigAnnotations(service)
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 supported controller
for _, obj := range objects {
kubernetes.UpdateController(obj, fillTemplate, fillObjectMeta)
}
// 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 {
objects = append(objects, sc)
}
allobjects = append(allobjects, objects...)
}
return allobjects
}

View File

@ -0,0 +1,26 @@
/*
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 transformer
import (
"github.com/skippbox/kompose/pkg/kobject"
"k8s.io/kubernetes/pkg/runtime"
)
type Transformer interface {
Transform(kobject.KomposeObject, kobject.ConvertOptions) []runtime.Object
}

153
pkg/transformer/utils.go Normal file
View File

@ -0,0 +1,153 @@
package transformer
import (
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"os"
"strings"
"github.com/Sirupsen/logrus"
"github.com/ghodss/yaml"
"github.com/skippbox/kompose/pkg/kobject"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
)
const letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789"
// RandStringBytes generates randomly n-character string
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
// Create the file to write to if --out is specified
func CreateOutFile(out string) *os.File {
var f *os.File
var err error
if len(out) != 0 {
f, err = os.Create(out)
if err != nil {
logrus.Fatalf("error opening file: %v", err)
}
}
return f
}
// Configure the container commands
func ConfigCommands(service kobject.ServiceConfig) []string {
var cmds []string
for _, cmd := range service.Command {
cmds = append(cmds, cmd)
}
return cmds
}
// parseVolume parse a given volume, which might be [name:][host:]container[:access_mode]
func ParseVolume(volume string) (name, host, container, mode string, err error) {
separator := ":"
volumeStrings := strings.Split(volume, separator)
if len(volumeStrings) == 0 {
return
}
// Set name if existed
if !isPath(volumeStrings[0]) {
name = volumeStrings[0]
volumeStrings = volumeStrings[1:]
}
if len(volumeStrings) == 0 {
err = fmt.Errorf("invalid volume format: %s", volume)
return
}
if volumeStrings[len(volumeStrings)-1] == "rw" || volumeStrings[len(volumeStrings)-1] == "ro" {
mode = volumeStrings[len(volumeStrings)-1]
volumeStrings = volumeStrings[:len(volumeStrings)-1]
}
container = volumeStrings[len(volumeStrings)-1]
volumeStrings = volumeStrings[:len(volumeStrings)-1]
if len(volumeStrings) == 1 {
host = volumeStrings[0]
}
if !isPath(container) || (len(host) > 0 && !isPath(host)) || len(volumeStrings) > 1 {
err = fmt.Errorf("invalid volume format: %s", volume)
return
}
return
}
func isPath(substring string) bool {
return strings.Contains(substring, "/")
}
// Configure label
func ConfigLabels(name string) map[string]string {
return map[string]string{"service": name}
}
// Configure annotations
func ConfigAnnotations(service kobject.ServiceConfig) map[string]string {
annotations := map[string]string{}
for key, value := range service.Annotations {
annotations[key] = value
}
return annotations
}
// Transform data to json/yaml
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}
versionedObj, err := api.Scheme.ConvertToVersion(obj, version)
if err != nil {
return nil, err
}
// convert data to json / yaml
data, err := json.MarshalIndent(versionedObj, "", " ")
if GenerateYaml == true {
data, err = yaml.Marshal(versionedObj)
}
if err != nil {
return nil, err
}
logrus.Debugf("%s\n", data)
return data, nil
}
// Either print to stdout or to file/s
func Print(name, trailing string, data []byte, toStdout, generateYaml bool, f *os.File) string {
file := ""
if generateYaml {
file = fmt.Sprintf("%s-%s.yaml", name, trailing)
} else {
file = fmt.Sprintf("%s-%s.json", name, trailing)
}
if toStdout {
fmt.Fprintf(os.Stdout, "%s\n", string(data))
return ""
} else if f != nil {
// Write all content to a single file f
if _, err := f.WriteString(fmt.Sprintf("%s\n", string(data))); err != nil {
logrus.Fatalf("Failed to write %s to file: %v", trailing, err)
}
f.Sync()
} else {
// Write content separately to each file
if err := ioutil.WriteFile(file, []byte(data), 0644); err != nil {
logrus.Fatalf("Failed to write %s: %v", trailing, err)
}
logrus.Printf("file %q created", file)
}
return file
}

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"
)