Skip to content

Commit

Permalink
Support flexible port mapping (#1585)
Browse files Browse the repository at this point in the history
* Support flexible port mapping

Signed-off-by: Javier López Barba <javier@okteto.com>

* Fix issue when portmapping same port

Signed-off-by: Javier López Barba <javier@okteto.com>

* Manage flexible ports errors

Signed-off-by: Javier López Barba <javier@okteto.com>

* Remove nested if

Signed-off-by: Javier López Barba <javier@okteto.com>
  • Loading branch information
jLopezbarb committed Jun 2, 2021
1 parent 1a1341c commit 278f7af
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 110 deletions.
4 changes: 2 additions & 2 deletions pkg/cmd/stack/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,8 @@ func addHiddenExposedPorts(ctx context.Context, s *model.Stack) {
if svc.Image != "" {
exposedPorts := registry.GetHiddenExposePorts(ctx, s.Namespace, svc.Image)
for _, port := range exposedPorts {
if !svc.IsAlreadyAdded(port) {
svc.Ports = append(svc.Ports, model.Port{Port: port, Protocol: apiv1.ProtocolTCP})
if !model.IsAlreadyAdded(port, svc.Ports) {
svc.Ports = append(svc.Ports, port)
}
}
}
Expand Down
58 changes: 39 additions & 19 deletions pkg/cmd/stack/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,15 +500,13 @@ func getInitContainerCommandAndVolumeMounts(svc model.Service) ([]string, []apiv
command += " && chmod 777 /volumes/*"
}
}
} else {
if !addedDataVolume {
volumeMounts = append(volumeMounts, apiv1.VolumeMount{Name: volumeName, MountPath: "/data"})
if command == "" {
command = "chmod 777 /data"
addedDataVolume = true
} else {
command += " && chmod 777 /data"
}
} else if !addedDataVolume {
volumeMounts = append(volumeMounts, apiv1.VolumeMount{Name: volumeName, MountPath: "/data"})
if command == "" {
command = "chmod 777 /data"
addedDataVolume = true
} else {
command += " && chmod 777 /data"
}
}
}
Expand Down Expand Up @@ -791,27 +789,49 @@ func translateServiceEnvironment(svc *model.Service) []apiv1.EnvVar {
func translateContainerPorts(svc *model.Service) []apiv1.ContainerPort {
result := []apiv1.ContainerPort{}
for _, p := range svc.Ports {
result = append(result, apiv1.ContainerPort{ContainerPort: p.Port})
result = append(result, apiv1.ContainerPort{ContainerPort: p.ContainerPort})
}
return result
}

func translateServicePorts(svc model.Service) []apiv1.ServicePort {
result := []apiv1.ServicePort{}
for _, p := range svc.Ports {
result = append(
result,
apiv1.ServicePort{
Name: fmt.Sprintf("p-%d-%s", p.Port, strings.ToLower(fmt.Sprintf("%v", p.Protocol))),
Port: int32(p.Port),
TargetPort: intstr.IntOrString{IntVal: p.Port},
Protocol: p.Protocol,
},
)
if !isServicePortAdded(p.ContainerPort, result) {
result = append(
result,
apiv1.ServicePort{
Name: fmt.Sprintf("p-%d-%d-%s", p.ContainerPort, p.ContainerPort, strings.ToLower(fmt.Sprintf("%v", p.Protocol))),
Port: int32(p.ContainerPort),
TargetPort: intstr.IntOrString{IntVal: p.ContainerPort},
Protocol: p.Protocol,
},
)
}
if p.HostPort != 0 && p.ContainerPort != p.HostPort && !isServicePortAdded(p.HostPort, result) {
result = append(
result,
apiv1.ServicePort{
Name: fmt.Sprintf("p-%d-%d-%s", p.HostPort, p.ContainerPort, strings.ToLower(fmt.Sprintf("%v", p.Protocol))),
Port: int32(p.HostPort),
TargetPort: intstr.IntOrString{IntVal: p.ContainerPort},
Protocol: p.Protocol,
},
)
}
}
return result
}

func isServicePortAdded(newPort int32, existentPorts []apiv1.ServicePort) bool {
for _, p := range existentPorts {
if p.Port == newPort {
return true
}
}
return false
}

func translateResources(svc *model.Service) apiv1.ResourceRequirements {
result := apiv1.ResourceRequirements{}
if svc.Resources != nil {
Expand Down
20 changes: 13 additions & 7 deletions pkg/cmd/stack/translate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func Test_translateDeployment(t *testing.T) {
Value: "value2",
},
},
Ports: []model.Port{{Port: 80}, {Port: 90}},
Ports: []model.Port{{ContainerPort: 80}, {ContainerPort: 90}},
},
},
}
Expand Down Expand Up @@ -261,7 +261,7 @@ func Test_translateStatefulSet(t *testing.T) {
Value: "value2",
},
},
Ports: []model.Port{{Port: 80}, {Port: 90}},
Ports: []model.Port{{ContainerPort: 80}, {ContainerPort: 90}},
CapAdd: []apiv1.Capability{apiv1.Capability("CAP_ADD")},
CapDrop: []apiv1.Capability{apiv1.Capability("CAP_DROP")},

Expand Down Expand Up @@ -462,7 +462,7 @@ func Test_translateJob(t *testing.T) {
Value: "value2",
},
},
Ports: []model.Port{{Port: 80}, {Port: 90}},
Ports: []model.Port{{ContainerPort: 80}, {ContainerPort: 90}},
CapAdd: []apiv1.Capability{apiv1.Capability("CAP_ADD")},
CapDrop: []apiv1.Capability{apiv1.Capability("CAP_DROP")},
RestartPolicy: apiv1.RestartPolicyNever,
Expand Down Expand Up @@ -614,8 +614,8 @@ func Test_translateJob(t *testing.T) {
}

func Test_translateService(t *testing.T) {
p1 := model.Port{Port: 80, Protocol: apiv1.ProtocolTCP}
p2 := model.Port{Port: 90, Protocol: apiv1.ProtocolTCP}
p1 := model.Port{HostPort: 82, ContainerPort: 80, Protocol: apiv1.ProtocolTCP}
p2 := model.Port{ContainerPort: 90, Protocol: apiv1.ProtocolTCP}
s := &model.Stack{
Name: "stackName",
Services: map[string]*model.Service{
Expand Down Expand Up @@ -654,13 +654,19 @@ func Test_translateService(t *testing.T) {
}
ports := []apiv1.ServicePort{
{
Name: "p-80-tcp",
Name: "p-80-80-tcp",
Port: 80,
TargetPort: intstr.IntOrString{IntVal: 80},
Protocol: apiv1.ProtocolTCP,
},
{
Name: "p-90-tcp",
Name: "p-82-80-tcp",
Port: 82,
TargetPort: intstr.IntOrString{IntVal: 80},
Protocol: apiv1.ProtocolTCP,
},
{
Name: "p-90-90-tcp",
Port: 90,
TargetPort: intstr.IntOrString{IntVal: 90},
Protocol: apiv1.ProtocolTCP,
Expand Down
37 changes: 16 additions & 21 deletions pkg/model/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"time"

"github.com/okteto/okteto/pkg/k8s/labels"
"github.com/okteto/okteto/pkg/log"
yaml "gopkg.in/yaml.v2"
apiv1 "k8s.io/api/core/v1"
resource "k8s.io/apimachinery/pkg/api/resource"
Expand Down Expand Up @@ -53,7 +54,6 @@ type Service struct {
EnvFiles EnvFiles `yaml:"env_file,omitempty"`

Environment Environment `yaml:"environment,omitempty"`
Expose []int32 `yaml:"expose,omitempty"`
Image string `yaml:"image,omitempty"`
Labels Labels `json:"labels,omitempty" yaml:"labels,omitempty"`
Annotations Annotations `json:"annotations,omitempty" yaml:"annotations,omitempty"`
Expand Down Expand Up @@ -111,8 +111,9 @@ type Quantity struct {
}

type Port struct {
Port int32
Protocol apiv1.Protocol
HostPort int32
ContainerPort int32
Protocol apiv1.Protocol
}

type EndpointSpec map[string]Endpoint
Expand Down Expand Up @@ -182,7 +183,6 @@ func GetStack(name, stackPath string, isCompose bool) (*Stack, error) {
}

for _, svc := range s.Services {
svc.extendPorts()
if svc.Build == nil {
continue
}
Expand Down Expand Up @@ -235,13 +235,6 @@ func ReadStack(bytes []byte, isCompose bool) (*Stack, error) {
svc.Replicas = 1
}

if len(svc.Expose) > 0 && len(svc.Ports) == 0 {
svc.Public = false
}

if len(svc.Expose) > 0 {
svc.extendPorts()
}
if svc.RestartPolicy != apiv1.RestartPolicyAlways {
for idx, volume := range svc.Volumes {
volumeName := fmt.Sprintf("pvc-%s-0", svcName)
Expand Down Expand Up @@ -350,7 +343,7 @@ func (s *Stack) GetConfigMapName() string {

func IsPortInService(port int32, ports []Port) bool {
for _, p := range ports {
if p.Port == port {
if p.ContainerPort == port {
return true
}
}
Expand All @@ -365,19 +358,21 @@ func (svc *Service) SetLastBuiltAnnotation() {
svc.Annotations[labels.LastBuiltAnnotation] = time.Now().UTC().Format(labels.TimeFormat)
}

//extendPorts adds the ports that are in expose field to the port list.
func (svc *Service) extendPorts() {
for _, port := range svc.Expose {
if !svc.IsAlreadyAdded(port) {
svc.Ports = append(svc.Ports, Port{Port: port, Protocol: apiv1.ProtocolTCP})
//isAlreadyAdded checks if a port is already on port list
func IsAlreadyAdded(p Port, ports []Port) bool {
for _, port := range ports {
if port.ContainerPort == p.ContainerPort {
log.Infof("Port '%d:%d' is already declared on port '%d:%d'", p.HostPort, p.HostPort, port.HostPort, port.ContainerPort)
return true
}
}
return false
}

//isAlreadyAdded checks if a port is already on port list
func (svc *Service) IsAlreadyAdded(p int32) bool {
for _, port := range svc.Ports {
if port.Port == p {
func IsAlreadyAddedExpose(p Port, ports []Port) bool {
for _, port := range ports {
if port.ContainerPort == p.ContainerPort || port.ContainerPort == p.HostPort || port.HostPort == p.HostPort || port.HostPort == p.ContainerPort {
log.Infof("Expose port '%d:%d' is already declared on port '%d:%d'", p.HostPort, p.HostPort, port.HostPort, port.ContainerPort)
return true
}
}
Expand Down
98 changes: 50 additions & 48 deletions pkg/model/stack_serializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ type ServiceRaw struct {
EnvFilesSneakCase EnvFiles `yaml:"env_file,omitempty"`
EnvFiles EnvFiles `yaml:"envFile,omitempty"`
Environment Environment `yaml:"environment,omitempty"`
Expose *RawMessage `yaml:"expose,omitempty"`
Expose []PortRaw `yaml:"expose,omitempty"`
Image string `yaml:"image,omitempty"`
Labels Labels `json:"labels,omitempty" yaml:"labels,omitempty"`
Annotations Annotations `json:"annotations,omitempty" yaml:"annotations,omitempty"`
Expand Down Expand Up @@ -382,15 +382,7 @@ func (serviceRaw *ServiceRaw) ToService(svcName string, stack *Stack) (*Service,

svc.Environment = serviceRaw.Environment

svc.Public = serviceRaw.Public
if !svc.Public && len(getAccessiblePorts(serviceRaw.Ports)) == 1 {
svc.Public = true
}
for _, p := range serviceRaw.Ports {
svc.Ports = append(svc.Ports, Port{Port: p.ContainerPort, Protocol: p.Protocol})
}

svc.Expose, err = unmarshalExpose(serviceRaw.Expose)
svc.Public, svc.Ports, err = getSvcPorts(serviceRaw.Public, serviceRaw.Ports, serviceRaw.Expose)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -433,6 +425,53 @@ func (serviceRaw *ServiceRaw) ToService(svcName string, stack *Stack) (*Service,
return svc, nil
}

func getSvcPorts(public bool, rawPorts, rawExpose []PortRaw) (bool, []Port, error) {
if !public && len(getAccessiblePorts(rawPorts)) == 1 {
public = true
}
if len(rawExpose) > 0 && len(rawPorts) == 0 {
public = false
}
ports := make([]Port, 0)
for _, p := range rawPorts {
if err := validatePort(p, ports); err == nil {
ports = append(ports, Port{HostPort: p.HostPort, ContainerPort: p.ContainerPort, Protocol: p.Protocol})
} else {
return false, ports, err
}
}

for _, p := range rawExpose {
newPort := Port{HostPort: p.HostPort, ContainerPort: p.ContainerPort, Protocol: p.Protocol}
if p.ContainerPort == 0 {
if !IsAlreadyAdded(newPort, ports) {
ports = append(ports, newPort)
}
} else {
if !IsAlreadyAddedExpose(newPort, ports) {
ports = append(ports, newPort)
}
}
}
return public, ports, nil
}

func validatePort(newPort PortRaw, ports []Port) error {
for _, p := range ports {
if newPort.ContainerPort == p.HostPort {
return fmt.Errorf("Container port '%d' is already declared as host port in port '%d:%d'", newPort.ContainerPort, p.HostPort, p.ContainerPort)
}
if newPort.HostPort == p.ContainerPort {
if p.HostPort == 0 {
return fmt.Errorf("Host port '%d' is already declared as container port in port '%d'", newPort.HostPort, p.ContainerPort)
} else {
return fmt.Errorf("Host port '%d' is already declared as container port in port '%d:%d'", newPort.HostPort, p.HostPort, p.ContainerPort)
}
}
}
return nil
}

func isNamedVolumeDeclared(volume StackVolume) bool {
if volume.LocalPath != "" {
if strings.HasPrefix(volume.LocalPath, "/") {
Expand Down Expand Up @@ -572,7 +611,7 @@ func IsSkippablePort(port int32) bool {

// MarshalYAML Implements the marshaler interface of the yaml pkg.
func (p *Port) MarshalYAML() (interface{}, error) {
return Port{Port: p.Port, Protocol: p.Protocol}, nil
return Port{ContainerPort: p.ContainerPort, Protocol: p.Protocol}, nil
}

func getRestartPolicy(svcName string, deployInfo *DeployInfoRaw, restartPolicy string) (apiv1.RestartPolicy, error) {
Expand Down Expand Up @@ -695,43 +734,6 @@ func (s *StackResources) UnmarshalYAML(unmarshal func(interface{}) error) error
return nil
}

func unmarshalExpose(raw *RawMessage) ([]int32, error) {
exposeInInt := make([]int32, 0)
if raw == nil {
return exposeInInt, nil
}
err := raw.unmarshal(&exposeInInt)
if err == nil {
return exposeInInt, nil
}
var exposeInString []string
err = raw.unmarshal(&exposeInString)
if err != nil {
return exposeInInt, err
}

for _, expose := range exposeInString {
if strings.Contains(expose, "-") {
return exposeInInt, fmt.Errorf("Can not convert %s. Range ports are not supported.", expose)
}
parts := strings.Split(expose, ":")
var portString string
if len(parts) == 1 {
portString = parts[0]
} else if len(parts) <= 3 {
portString = parts[len(parts)-1]
} else {
return exposeInInt, fmt.Errorf(malformedPortForward, expose)
}
port, err := strconv.Atoi(portString)
if err != nil {
return exposeInInt, err
}
exposeInInt = append(exposeInInt, int32(port))
}
return exposeInInt, nil
}

func unmarshalDuration(raw *RawMessage) (int64, error) {
var duration int64
if raw == nil {
Expand Down

0 comments on commit 278f7af

Please sign in to comment.