Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions pkg/kobject/kobject.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,9 @@ type ServiceConfig struct {
CronJobBackoffLimit *int32 `compose:"kompose.cronjob.backoff_limit"`
Volumes []Volumes `compose:""`
Secrets []types.ServiceSecretConfig
HealthChecks HealthChecks `compose:""`
Placement Placement `compose:""`
HealthChecks HealthChecks `compose:""`
Placement Placement `compose:""`
HostAliases []HostAliases `compose:""`
//This is for long LONG SYNTAX link(https://docs.docker.com/compose/compose-file/#long-syntax)
Configs []types.ServiceConfigObjConfig `compose:""`
//This is for SHORT SYNTAX link(https://docs.docker.com/compose/compose-file/#configs)
Expand Down Expand Up @@ -216,6 +217,11 @@ type Ports struct {
Protocol string // Upper string
}

type HostAliases struct {
IP string
Hostnames []string
}

// ID returns an unique id for this port settings, to avoid conflict
func (port *Ports) ID() string {
return strconv.Itoa(int(port.ContainerPort)) + port.Protocol
Expand Down
29 changes: 29 additions & 0 deletions pkg/loader/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package compose
import (
"context"
"fmt"
"net"
"os"
"reflect"
"strconv"
Expand Down Expand Up @@ -314,6 +315,31 @@ func loadPorts(ports []types.ServicePortConfig, expose []string) []kobject.Ports
return komposePorts
}

// Convert extra hosts from compose to kobject.HostAliases
func loadExtraHosts(extraHosts types.HostsList) []kobject.HostAliases {
ipToHosts := make(map[string][]string)

for hostname, ips := range extraHosts {
for _, ip := range ips {
if net.ParseIP(ip) == nil {
log.Warnf("Extra hosts contains invalid IP address %q for hostname %q. Kubernetes HostAlias requires valid IPv4 or IPv6 address.", ip, hostname)
continue
}
ipToHosts[ip] = append(ipToHosts[ip], hostname)
}
}

hostAliases := make([]kobject.HostAliases, 0, len(ipToHosts))
for ip, hostnames := range ipToHosts {
hostAliases = append(hostAliases, kobject.HostAliases{
IP: ip,
Hostnames: hostnames,
})
}

return hostAliases
}

/*
Convert the HealthCheckConfig as designed by Docker to

Expand Down Expand Up @@ -587,6 +613,9 @@ func dockerComposeToKomposeMapping(composeObject *types.Project) (kobject.Kompos
return kobject.KomposeObject{}, err
}

// Parse extra hosts
serviceConfig.HostAliases = loadExtraHosts(composeServiceConfig.ExtraHosts)

// Log if the name will been changed
if normalizeServiceNames(name) != name {
log.Infof("Service name in docker-compose has been changed from %q to %q", name, normalizeServiceNames(name))
Expand Down
97 changes: 97 additions & 0 deletions pkg/loader/compose/compose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"os"
"reflect"
"sort"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -243,6 +244,102 @@ func TestLoadV3Ports(t *testing.T) {
}
}

func TestLoadExtraHosts(t *testing.T) {
for _, tt := range []struct {
desc string
extraHosts types.HostsList
want []kobject.HostAliases
}{
{
desc: "single host with IPv4",
extraHosts: types.HostsList{
"example.com": []string{"192.168.1.100"},
},
want: []kobject.HostAliases{
{IP: "192.168.1.100", Hostnames: []string{"example.com"}},
},
},
{
desc: "multiple hosts with the same IP",
extraHosts: types.HostsList{
"example.com": []string{"192.168.1.100"},
"api.example.com": []string{"192.168.1.100"},
},
want: []kobject.HostAliases{
{IP: "192.168.1.100", Hostnames: []string{"example.com", "api.example.com"}},
},
},
{
desc: "multiple hosts with different IPs",
extraHosts: types.HostsList{
"host1.com": []string{"192.168.1.100"},
"host2.com": []string{"192.168.1.101"},
},
want: []kobject.HostAliases{
{IP: "192.168.1.100", Hostnames: []string{"host1.com"}},
{IP: "192.168.1.101", Hostnames: []string{"host2.com"}},
},
},
{
desc: "IPv6 addresses",
extraHosts: types.HostsList{
"ipv6.example.com": []string{"2001:db8::1"},
"localhost6": []string{"::1"},
},
want: []kobject.HostAliases{
{IP: "2001:db8::1", Hostnames: []string{"ipv6.example.com"}},
{IP: "::1", Hostnames: []string{"localhost6"}},
},
},
{
desc: "mixed IPv4 and IPv6",
extraHosts: types.HostsList{
"ipv4.example.com": []string{"192.168.1.100"},
"ipv6.example.com": []string{"2001:db8::1"},
},
want: []kobject.HostAliases{
{IP: "192.168.1.100", Hostnames: []string{"ipv4.example.com"}},
{IP: "2001:db8::1", Hostnames: []string{"ipv6.example.com"}},
},
},
{
desc: "single host with multiple IPs",
extraHosts: types.HostsList{
"multi.example.com": []string{"192.168.1.100", "10.0.0.1"},
},
want: []kobject.HostAliases{
{IP: "192.168.1.100", Hostnames: []string{"multi.example.com"}},
{IP: "10.0.0.1", Hostnames: []string{"multi.example.com"}},
},
},
{
desc: "empty extra hosts",
extraHosts: types.HostsList{},
want: []kobject.HostAliases{},
},
} {
t.Run(tt.desc, func(t *testing.T) {
got := loadExtraHosts(tt.extraHosts)

sortHostAliases := func(ha []kobject.HostAliases) {
for i := range ha {
sort.Strings(ha[i].Hostnames)
}
sort.Slice(ha, func(i, j int) bool {
return ha[i].IP < ha[j].IP
})
}

sortHostAliases(got)
sortHostAliases(tt.want)

if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("loadExtraHosts() mismatch (-want +got):\n%s", diff)
}
})
}
}

// Test if service types are parsed properly on user input
// give a service type and expect correct input
func TestHandleServiceType(t *testing.T) {
Expand Down
12 changes: 12 additions & 0 deletions pkg/transformer/kubernetes/k8sutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,18 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
template.Spec.Subdomain = service.DomainName
}

// Configure hostAliases
if len(service.HostAliases) > 0 {
var hostAliases []api.HostAlias
for _, ha := range service.HostAliases {
hostAliases = append(hostAliases, api.HostAlias{
IP: ha.IP,
Hostnames: ha.Hostnames,
})
}
template.Spec.HostAliases = hostAliases
}

if serviceAccountName, ok := service.Labels[compose.LabelServiceAccountName]; ok {
template.Spec.ServiceAccountName = serviceAccountName
}
Expand Down
1 change: 1 addition & 0 deletions pkg/transformer/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,7 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
SecurityContext(groupName, service),
HostName(service),
DomainName(service),
HostAliases(service),
ResourcesLimits(service),
ResourcesRequests(service),
TerminationGracePeriodSeconds(groupName, service),
Expand Down
148 changes: 148 additions & 0 deletions pkg/transformer/kubernetes/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func newServiceConfig() kobject.ServiceConfig {
CapAdd: []string{"cap_add"},
CapDrop: []string{"cap_drop"},
Expose: []string{"expose"}, // not supported
HostAliases: []kobject.HostAliases{{IP: "127.0.0.1", Hostnames: []string{"localhost"}}},
Privileged: true,
Restart: "always",
ImagePullSecret: "regcred",
Expand Down Expand Up @@ -1322,3 +1323,150 @@ UNDEFINED_VAR=${MISSING_VAR:-default_value}
})
}
}

func TestHostAliases(t *testing.T) {
testCases := map[string]struct {
komposeObject kobject.KomposeObject
opt kobject.ConvertOptions
}{
"Convert with HostAliases": {
komposeObject: func() kobject.KomposeObject {
ko := newKomposeObject()
config := ko.ServiceConfigs["app"]
config.HostAliases = []kobject.HostAliases{
{IP: "127.0.0.1", Hostnames: []string{"localhost", "local"}},
{IP: "::1", Hostnames: []string{"ip6-localhost"}},
}
ko.ServiceConfigs["app"] = config
return ko
}(),
opt: kobject.ConvertOptions{CreateD: true},
},
}

for name, test := range testCases {
t.Log("Test case:", name)
k := Kubernetes{}
objs, err := k.Transform(test.komposeObject, test.opt)
if err != nil {
t.Error(errors.Wrap(err, "k.Transform failed"))
}

foundHostAliases := false
for _, obj := range objs {
if d, ok := obj.(*appsv1.Deployment); ok {
hostAliases := d.Spec.Template.Spec.HostAliases
if len(hostAliases) != 2 {
t.Errorf("Expected 2 HostAliases, got %d", len(hostAliases))
continue
}

if hostAliases[0].IP != "127.0.0.1" {
t.Errorf("Expected IP 127.0.0.1, got %s", hostAliases[0].IP)
}
if len(hostAliases[0].Hostnames) != 2 || hostAliases[0].Hostnames[0] != "localhost" || hostAliases[0].Hostnames[1] != "local" {
t.Errorf("Expected hostnames [localhost local], got %v", hostAliases[0].Hostnames)
}

if hostAliases[1].IP != "::1" {
t.Errorf("Expected IP ::1, got %s", hostAliases[1].IP)
}
if len(hostAliases[1].Hostnames) != 1 || hostAliases[1].Hostnames[0] != "ip6-localhost" {
t.Errorf("Expected hostnames [ip6-localhost], got %v", hostAliases[1].Hostnames)
}

foundHostAliases = true
}
}

if !foundHostAliases {
t.Error("Did not find HostAliases in generated Deployment")
}
}
}

func TestHostAliasesServiceGroup(t *testing.T) {
serviceName1 := "web"
serviceName2 := "db"
testCases := map[string]struct {
komposeObject kobject.KomposeObject
opt kobject.ConvertOptions
}{
"Service Group with HostAliases": {
komposeObject: func() kobject.KomposeObject {
ko := kobject.KomposeObject{
ServiceConfigs: map[string]kobject.ServiceConfig{
serviceName1: {
Name: serviceName1,
Image: "nginx",
Port: []kobject.Ports{{HostPort: 80, ContainerPort: 80}},
HostAliases: []kobject.HostAliases{
{IP: "10.0.0.1", Hostnames: []string{"web-alias"}},
},
GroupAdd: []int64{1000},
Labels: map[string]string{
"kompose.service.group": "mygroup",
},
},
serviceName2: {
Name: serviceName2,
Image: "postgres",
Port: []kobject.Ports{{HostPort: 5432, ContainerPort: 5432}},
HostAliases: []kobject.HostAliases{
{IP: "10.0.0.2", Hostnames: []string{"db-alias"}},
},
GroupAdd: []int64{1001},
Labels: map[string]string{
"kompose.service.group": "mygroup",
},
},
},
}
return ko
}(),
opt: kobject.ConvertOptions{ServiceGroupMode: "label", CreateD: true},
},
}

for name, test := range testCases {
t.Log("Test case:", name)
k := Kubernetes{}
objs, err := k.Transform(test.komposeObject, test.opt)
if err != nil {
t.Error(errors.Wrap(err, "k.Transform failed"))
}

foundHostAliases := false
for _, obj := range objs {
if d, ok := obj.(*appsv1.Deployment); ok {
hostAliases := d.Spec.Template.Spec.HostAliases
if len(hostAliases) != 2 {
t.Errorf("Expected 2 HostAliases, got %d", len(hostAliases))
continue
}

// Check first HostAlias
found1 := false
found2 := false
for _, ha := range hostAliases {
if ha.IP == "10.0.0.1" && len(ha.Hostnames) == 1 && ha.Hostnames[0] == "web-alias" {
found1 = true
}
if ha.IP == "10.0.0.2" && len(ha.Hostnames) == 1 && ha.Hostnames[0] == "db-alias" {
found2 = true
}
}

if !found1 || !found2 {
t.Errorf("Expected both HostAliases (10.0.0.1/web-alias and 10.0.0.2/db-alias), got %v", hostAliases)
}

foundHostAliases = true
}
}

if !foundHostAliases {
t.Error("Did not find HostAliases in generated Deployment for Service Group")
}
}
}
14 changes: 14 additions & 0 deletions pkg/transformer/kubernetes/podspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,20 @@ func DomainName(service kobject.ServiceConfig) PodSpecOption {
}
}

// HostAliases configure the host aliases of a pod
func HostAliases(service kobject.ServiceConfig) PodSpecOption {
return func(podSpec *PodSpec) {
if len(service.HostAliases) > 0 {
for _, ha := range service.HostAliases {
podSpec.HostAliases = append(podSpec.HostAliases, api.HostAlias{
IP: ha.IP,
Hostnames: ha.Hostnames,
})
}
}
}
}

func configProbe(healthCheck kobject.HealthCheck) *api.Probe {
probe := api.Probe{}
// We check to see if it's blank or disable
Expand Down
Loading
Loading