Add support for host:port:port

This adds support for supplying for example:
"127.0.0.1:80:80/tcp" to docker-compose.yaml and converting it to it's
corresponding Kubernetes / OpenShift hostIP.

This commit also refactors the loadPorts function of compose.go

Closes https://github.com/kubernetes-incubator/kompose/issues/335
This commit is contained in:
Charlie Drage 2017-02-09 12:21:17 -05:00
parent be042c7e1f
commit 438088f37d
7 changed files with 287 additions and 20 deletions

View File

@ -91,5 +91,6 @@ type EnvVar struct {
type Ports struct { type Ports struct {
HostPort int32 HostPort int32
ContainerPort int32 ContainerPort int32
HostIP string
Protocol api.Protocol Protocol api.Protocol
} }

View File

@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -18,6 +18,7 @@ package compose
import ( import (
"fmt" "fmt"
"net"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
@ -180,41 +181,86 @@ func loadEnvVars(envars []string) []kobject.EnvVar {
func loadPorts(composePorts []string) ([]kobject.Ports, error) { func loadPorts(composePorts []string) ([]kobject.Ports, error) {
ports := []kobject.Ports{} ports := []kobject.Ports{}
character := ":" character := ":"
// For each port listed
for _, port := range composePorts { for _, port := range composePorts {
// Get the TCP / UDP protocol. Checks to see if it splits in 2 with '/' character.
// ex. 15000:15000/tcp
// else, set a default protocol of using TCP
proto := api.ProtocolTCP proto := api.ProtocolTCP
// get protocol protocolCheck := strings.Split(port, "/")
p := strings.Split(port, "/") if len(protocolCheck) == 2 {
if len(p) == 2 { if strings.EqualFold("tcp", protocolCheck[1]) {
if strings.EqualFold("tcp", p[1]) {
proto = api.ProtocolTCP proto = api.ProtocolTCP
} else if strings.EqualFold("udp", p[1]) { } else if strings.EqualFold("udp", protocolCheck[1]) {
proto = api.ProtocolUDP proto = api.ProtocolUDP
} else {
return nil, fmt.Errorf("invalid protocol %q", protocolCheck[1])
} }
} }
// port mappings without protocol part
portNoProto := p[0] // Split up the ports / IP without the "/tcp" or "/udp" appended to it
if strings.Contains(portNoProto, character) { justPorts := strings.Split(protocolCheck[0], character)
hostPort := portNoProto[0:strings.Index(portNoProto, character)]
hostPort = strings.TrimSpace(hostPort) if len(justPorts) == 3 {
hostPortInt, err := strconv.Atoi(hostPort) // ex. 127.0.0.1:80:80
if err != nil {
return nil, fmt.Errorf("invalid host port %q", port) // Get the IP address
hostIP := justPorts[0]
ip := net.ParseIP(hostIP)
if ip.To4() == nil && ip.To16() == nil {
return nil, fmt.Errorf("%q contains an invalid IPv4 or IPv6 IP address", port)
} }
containerPort := portNoProto[strings.Index(portNoProto, character)+1:]
containerPort = strings.TrimSpace(containerPort) // Get the host port
containerPortInt, err := strconv.Atoi(containerPort) hostPortInt, err := strconv.Atoi(justPorts[1])
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid container port %q", port) return nil, fmt.Errorf("invalid host port %q valid example: 127.0.0.1:80:80", port)
} }
// Get the container port
containerPortInt, err := strconv.Atoi(justPorts[2])
if err != nil {
return nil, fmt.Errorf("invalid container port %q valid example: 127.0.0.1:80:80", port)
}
// Convert to a kobject struct with ports as well as IP
ports = append(ports, kobject.Ports{
HostPort: int32(hostPortInt),
ContainerPort: int32(containerPortInt),
HostIP: hostIP,
Protocol: proto,
})
} else if len(justPorts) == 2 {
// ex. 80:80
// Get the host port
hostPortInt, err := strconv.Atoi(justPorts[0])
if err != nil {
return nil, fmt.Errorf("invalid host port %q valid example: 80:80", port)
}
// Get the container port
containerPortInt, err := strconv.Atoi(justPorts[1])
if err != nil {
return nil, fmt.Errorf("invalid container port %q valid example: 80:80", port)
}
// Convert to a kobject struct and add to the list of ports
ports = append(ports, kobject.Ports{ ports = append(ports, kobject.Ports{
HostPort: int32(hostPortInt), HostPort: int32(hostPortInt),
ContainerPort: int32(containerPortInt), ContainerPort: int32(containerPortInt),
Protocol: proto, Protocol: proto,
}) })
} else { } else {
containerPortInt, err := strconv.Atoi(portNoProto) // ex. 80
containerPortInt, err := strconv.Atoi(justPorts[0])
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid container port %q", port) return nil, fmt.Errorf("invalid container port %q valid example: 80", port)
} }
ports = append(ports, kobject.Ports{ ports = append(ports, kobject.Ports{
ContainerPort: int32(containerPortInt), ContainerPort: int32(containerPortInt),

View File

@ -23,6 +23,7 @@ import (
"testing" "testing"
"github.com/kubernetes-incubator/kompose/pkg/kobject" "github.com/kubernetes-incubator/kompose/pkg/kobject"
"k8s.io/kubernetes/pkg/api"
"github.com/docker/libcompose/config" "github.com/docker/libcompose/config"
"github.com/docker/libcompose/project" "github.com/docker/libcompose/project"
@ -53,6 +54,55 @@ func TestHandleServiceType(t *testing.T) {
} }
} }
// Test loading of ports
func TestLoadPorts(t *testing.T) {
port1 := []string{"127.0.0.1:80:80/tcp"}
result1 := kobject.Ports{
HostIP: "127.0.0.1",
HostPort: 80,
ContainerPort: 80,
Protocol: api.ProtocolTCP,
}
port2 := []string{"80:80/tcp"}
result2 := kobject.Ports{
HostPort: 80,
ContainerPort: 80,
Protocol: api.ProtocolTCP,
}
port3 := []string{"80:80"}
result3 := kobject.Ports{
HostPort: 80,
ContainerPort: 80,
Protocol: api.ProtocolTCP,
}
port4 := []string{"80"}
result4 := kobject.Ports{
HostPort: 0,
ContainerPort: 80,
Protocol: api.ProtocolTCP,
}
tests := []struct {
ports []string
result kobject.Ports
}{
{port1, result1},
{port2, result2},
{port3, result3},
{port4, result4},
}
for _, tt := range tests {
result, err := loadPorts(tt.ports)
if err != nil {
t.Errorf("Unexpected error with loading ports %v", err)
}
if result[0] != tt.result {
t.Errorf("Expected %q, got %q", tt.result, result[0])
}
}
}
func TestLoadEnvVar(t *testing.T) { func TestLoadEnvVar(t *testing.T) {
ev1 := []string{"foo=bar"} ev1 := []string{"foo=bar"}
rs1 := kobject.EnvVar{ rs1 := kobject.EnvVar{

View File

@ -285,13 +285,16 @@ func (k *Kubernetes) ConfigPorts(name string, service kobject.ServiceConfig) []a
if port.Protocol == api.ProtocolTCP { if port.Protocol == api.ProtocolTCP {
ports = append(ports, api.ContainerPort{ ports = append(ports, api.ContainerPort{
ContainerPort: port.ContainerPort, ContainerPort: port.ContainerPort,
HostIP: port.HostIP,
}) })
} else { } else {
ports = append(ports, api.ContainerPort{ ports = append(ports, api.ContainerPort{
ContainerPort: port.ContainerPort, ContainerPort: port.ContainerPort,
Protocol: port.Protocol, Protocol: port.Protocol,
HostIP: port.HostIP,
}) })
} }
} }
return ports return ports
@ -304,6 +307,7 @@ func (k *Kubernetes) ConfigServicePorts(name string, service kobject.ServiceConf
if port.HostPort == 0 { if port.HostPort == 0 {
port.HostPort = port.ContainerPort port.HostPort = port.ContainerPort
} }
var targetPort intstr.IntOrString var targetPort intstr.IntOrString
targetPort.IntVal = port.ContainerPort targetPort.IntVal = port.ContainerPort
targetPort.StrVal = strconv.Itoa(int(port.ContainerPort)) targetPort.StrVal = strconv.Itoa(int(port.ContainerPort))

View File

@ -123,6 +123,11 @@ export $(cat $KOMPOSE_ROOT/script/test/fixtures/keyonly-envs/envs)
convert::expect_success "kompose --file $KOMPOSE_ROOT/script/test/fixtures/keyonly-envs/env.yml convert --stdout -j" "$KOMPOSE_ROOT/script/test/fixtures/keyonly-envs/output-k8s.json" convert::expect_success "kompose --file $KOMPOSE_ROOT/script/test/fixtures/keyonly-envs/env.yml convert --stdout -j" "$KOMPOSE_ROOT/script/test/fixtures/keyonly-envs/output-k8s.json"
unset $(cat $KOMPOSE_ROOT/script/test/fixtures/keyonly-envs/envs | cut -d'=' -f1) unset $(cat $KOMPOSE_ROOT/script/test/fixtures/keyonly-envs/envs | cut -d'=' -f1)
#####
# Test related to host:port:container in docker-compose
# kubernetes test
convert::expect_success "kompose -f $KOMPOSE_ROOT/script/test/fixtures/ports-with-ip/docker-compose.yml convert --stdout -j" "$KOMPOSE_ROOT/script/test/fixtures/ports-with-ip/output-k8s.json"
###### ######
# Test related to "stdin_open: true" in docker-compose # Test related to "stdin_open: true" in docker-compose

View File

@ -0,0 +1,19 @@
version: "2"
services:
web:
image: tuna/docker-counter23
ports:
- "127.0.0.1:5000:5000/tcp"
links:
- redis
networks:
- default
redis:
image: redis:3.0
networks:
- default
ports:
- "6379/tcp"
- "1234:1235/udp"

View File

@ -0,0 +1,142 @@
{
"kind": "List",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "redis",
"creationTimestamp": null,
"labels": {
"service": "redis"
}
},
"spec": {
"ports": [
{
"name": "6379",
"port": 6379,
"targetPort": 6379
},
{
"name": "1234",
"protocol": "UDP",
"port": 1234,
"targetPort": 1235
}
],
"selector": {
"service": "redis"
}
},
"status": {
"loadBalancer": {}
}
},
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "web",
"creationTimestamp": null,
"labels": {
"service": "web"
}
},
"spec": {
"ports": [
{
"name": "5000",
"port": 5000,
"targetPort": 5000
}
],
"selector": {
"service": "web"
}
},
"status": {
"loadBalancer": {}
}
},
{
"kind": "Deployment",
"apiVersion": "extensions/v1beta1",
"metadata": {
"name": "redis",
"creationTimestamp": null
},
"spec": {
"replicas": 1,
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"service": "redis"
}
},
"spec": {
"containers": [
{
"name": "redis",
"image": "redis:3.0",
"ports": [
{
"containerPort": 6379
},
{
"containerPort": 1235,
"protocol": "UDP"
}
],
"resources": {}
}
],
"restartPolicy": "Always"
}
},
"strategy": {}
},
"status": {}
},
{
"kind": "Deployment",
"apiVersion": "extensions/v1beta1",
"metadata": {
"name": "web",
"creationTimestamp": null
},
"spec": {
"replicas": 1,
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"service": "web"
}
},
"spec": {
"containers": [
{
"name": "web",
"image": "tuna/docker-counter23",
"ports": [
{
"containerPort": 5000,
"hostIP": "127.0.0.1"
}
],
"resources": {}
}
],
"restartPolicy": "Always"
}
},
"strategy": {}
},
"status": {}
}
]
}