mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
Use custom address for port forwarding (#6766)
* Custom address for port forwarding Signed-off-by: Parthvi Vala <pvala@redhat.com> * Add integration tests Signed-off-by: Parthvi Vala <pvala@redhat.com> * Use private variables for custom address and ports Signed-off-by: Parthvi Vala <pvala@redhat.com> * Update pkg/util/util.go Co-authored-by: Armel Soro <armel@rm3l.org> * Assign custom address to HostIP for podman Signed-off-by: Parthvi Vala <pvala@redhat.com> * Add validation for free port when --port-forward is provided in the Validate() method, add integration test for running parallel dev sessions on same platform, same port and different addresses Signed-off-by: Parthvi Vala <pvala@redhat.com> * Fix unit test failure Signed-off-by: Parthvi Vala <pvala@redhat.com> * Attempt at fixing windows failure Signed-off-by: Parthvi Vala <pvala@redhat.com> * Add documentation Signed-off-by: Parthvi Vala <pvala@redhat.com> * Use default value for --address flag Signed-off-by: Parthvi Vala <pvala@redhat.com> * Changes from review Co-authored-by: Armel Soro <asoro@redhat.com> Signed-off-by: Parthvi Vala <pvala@redhat.com> --------- Signed-off-by: Parthvi Vala <pvala@redhat.com> Co-authored-by: Armel Soro <armel@rm3l.org> Co-authored-by: Armel Soro <asoro@redhat.com>
This commit is contained in:
@@ -244,6 +244,114 @@ The following command will override the `USER` Devfile variable with the `john`
|
||||
odo dev --var USER=john --var-file config.vars
|
||||
```
|
||||
|
||||
|
||||
### Using custom port mapping for port forwarding
|
||||
Custom local ports can be passed for port forwarding with the help of the `--port-forward` flag. This feature is supported on both podman and cluster.
|
||||
|
||||
This feature can be helpful when you want to provide consistent and predictable port numbers and avoid being assigned a potentially different port number every time `odo dev` is run.
|
||||
|
||||
Supported formats for this flag include:
|
||||
1. `<LOCAL_PORT>:<CONTAINER_PORT>`
|
||||
2. `<LOCAL_PORT>:<CONTAINER_NAME>:<CONTAINER_PORT>` - This format is necessary when multiple container components of a Devfile have the same port number.
|
||||
|
||||
The flag accepts a stringArray, so `--port-forward` flag can be defined multiple times.
|
||||
|
||||
If a custom port mapping is not defined for a port, `odo` will assign a free port in the range of 20001-30001.
|
||||
|
||||
```shell
|
||||
odo dev --port-forward <LOCAL_PORT_1>:<CONTAINER_PORT_1> --port-forward <LOCAL_PORT_2>:<CONTAINER_NAME>:<CONTAINER_PORT_2>
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
```shell
|
||||
$ odo dev --port-forward 3000:runtime:3000 --port-forward 5000:5858 --debug
|
||||
__
|
||||
/ \__ Developing using the "my-nodejs-app" Devfile
|
||||
\__/ \ Namespace: default
|
||||
/ \__/ odo version: v3.9.0
|
||||
\__/
|
||||
|
||||
⚠ You are using "default" namespace, odo may not work as expected in the default namespace.
|
||||
⚠ You may set a new namespace by running `odo create namespace <name>`, or set an existing one by running `odo set namespace <name>`
|
||||
|
||||
↪ Running on the cluster in Dev mode
|
||||
• Waiting for Kubernetes resources ...
|
||||
⚠ Pod is Pending
|
||||
✓ Pod is Running
|
||||
✓ Syncing files into the container [152ms]
|
||||
✓ Building your application in container (command: install) [27s]
|
||||
• Executing the application (command: debug) ...
|
||||
✓ Waiting for the application to be ready [1s]
|
||||
- Forwarding from 127.0.0.1:8000 -> 3000
|
||||
|
||||
- Forwarding from 127.0.0.1:5000 -> 5858
|
||||
|
||||
|
||||
↪ Dev mode
|
||||
Status:
|
||||
Watching for changes in the current directory /tmp/nodejs-debug-2
|
||||
|
||||
Keyboard Commands:
|
||||
[Ctrl+c] - Exit and delete resources from the cluster
|
||||
[p] - Manually apply local changes to the application on the cluster
|
||||
```
|
||||
</details>
|
||||
|
||||
Note that `--random-ports` flag cannot be used with `--port-forward` flag.
|
||||
|
||||
### Using custom address for port forwarding
|
||||
A custom address can be passed for port forwarding with the help of `--address` flag. This feature is supported on both podman and cluster.
|
||||
The default value is 127.0.0.1.
|
||||
|
||||
```shell
|
||||
odo dev --address <IP_ADDRESS>
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
```shell
|
||||
$ odo dev --address 127.0.10.3
|
||||
__
|
||||
/ \__ Developing using the "my-nodejs-app" Devfile
|
||||
\__/ \ Namespace: default
|
||||
/ \__/ odo version: v3.9.0
|
||||
\__/
|
||||
|
||||
⚠ You are using "default" namespace, odo may not work as expected in the default namespace.
|
||||
⚠ You may set a new namespace by running `odo create namespace <name>`, or set an existing one by running `odo set namespace <name>`
|
||||
|
||||
↪ Running on the cluster in Dev mode
|
||||
• Waiting for Kubernetes resources ...
|
||||
⚠ Pod is Pending
|
||||
✓ Pod is Running
|
||||
✓ Syncing files into the container [123ms]
|
||||
✓ Building your application in container (command: install) [15s]
|
||||
• Executing the application (command: run) ...
|
||||
✓ Waiting for the application to be ready [1s]
|
||||
- Forwarding from 127.0.10.3:20001 -> 3000
|
||||
|
||||
|
||||
↪ Dev mode
|
||||
Status:
|
||||
Watching for changes in the current directory /tmp/nodejs-debug-2
|
||||
|
||||
Keyboard Commands:
|
||||
[Ctrl+c] - Exit and delete resources from the cluster
|
||||
[p] - Manually apply local changes to the application on the cluster
|
||||
```
|
||||
</details>
|
||||
|
||||
:::note
|
||||
If you are on macOS and using a Cluster platform, you may not be able to run multiple Dev sessions in parallel on address 0.0.0.0 without defining a custom port mapping, or using a different or default address.
|
||||
|
||||
For more information, see the following issues:
|
||||
1. [Cannot start 2 different Dev sessions on Podman due to conflicting host ports](https://github.com/redhat-developer/odo/issues/6612)
|
||||
2. [[MacOS] Cannot run 2 dev sessions simultaneously on cluster](https://github.com/redhat-developer/odo/issues/6744)
|
||||
:::
|
||||
|
||||
### Running on Podman
|
||||
|
||||
Instead of deploying the container into a Kubernetes cluster, `odo dev` can leverage the podman installation on your system to deploy the container.
|
||||
|
||||
@@ -22,6 +22,8 @@ type StartOptions struct {
|
||||
RandomPorts bool
|
||||
// CustomForwardedPorts define custom ports for port forwarding
|
||||
CustomForwardedPorts []api.ForwardedPort
|
||||
// CustomAddress defines a custom local address for port forwarding; default value is 127.0.0.1
|
||||
CustomAddress string
|
||||
// if WatchFiles is set, files changes will trigger a new sync to the container
|
||||
WatchFiles bool
|
||||
// IgnoreLocalhost indicates whether to proceed with port-forwarding regardless of any container ports being bound to the container loopback interface.
|
||||
|
||||
@@ -180,7 +180,7 @@ func (o *DevClient) innerloop(ctx context.Context, parameters common.PushParamet
|
||||
fmt.Fprintln(log.GetStdout())
|
||||
}
|
||||
|
||||
err = o.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.StartOptions.Debug, parameters.StartOptions.RandomPorts, log.GetStdout(), parameters.StartOptions.ErrOut, parameters.StartOptions.CustomForwardedPorts)
|
||||
err = o.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.StartOptions.Debug, parameters.StartOptions.RandomPorts, log.GetStdout(), parameters.StartOptions.ErrOut, parameters.StartOptions.CustomForwardedPorts, parameters.StartOptions.CustomAddress)
|
||||
if err != nil {
|
||||
return common.NewErrPortForward(err)
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ func createPodFromComponent(
|
||||
randomPorts bool,
|
||||
customForwardedPorts []api.ForwardedPort,
|
||||
usedPorts []int,
|
||||
customAddress string,
|
||||
) (*corev1.Pod, []api.ForwardedPort, error) {
|
||||
var (
|
||||
appName = odocontext.GetApplication(ctx)
|
||||
@@ -60,7 +61,7 @@ func createPodFromComponent(
|
||||
}
|
||||
|
||||
var fwPorts []api.ForwardedPort
|
||||
fwPorts, err = getPortMapping(*devfileObj, debug, randomPorts, usedPorts, customForwardedPorts)
|
||||
fwPorts, err = getPortMapping(*devfileObj, debug, randomPorts, usedPorts, customForwardedPorts, customAddress)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -112,7 +113,7 @@ func createPodFromComponent(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
containers = addHostPorts(withHelperContainer, containers, fwPorts)
|
||||
containers = addHostPorts(withHelperContainer, containers, fwPorts, customAddress)
|
||||
|
||||
pod := corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
@@ -142,7 +143,10 @@ func createPodFromComponent(
|
||||
return &pod, fwPorts, nil
|
||||
}
|
||||
|
||||
func addHostPorts(withHelperContainer bool, containers []corev1.Container, fwPorts []api.ForwardedPort) []corev1.Container {
|
||||
func addHostPorts(withHelperContainer bool, containers []corev1.Container, fwPorts []api.ForwardedPort, customAddress string) []corev1.Container {
|
||||
if customAddress == "" {
|
||||
customAddress = "127.0.0.1"
|
||||
}
|
||||
if withHelperContainer {
|
||||
// A side helper container is added and will be responsible for redirecting the traffic,
|
||||
// so it can work even if the application is listening on the container loopback interface.
|
||||
@@ -164,6 +168,7 @@ func addHostPorts(withHelperContainer bool, containers []corev1.Container, fwPor
|
||||
Name: fwPort.PortName,
|
||||
ContainerPort: int32(fwPort.LocalPort),
|
||||
HostPort: int32(fwPort.LocalPort),
|
||||
HostIP: customAddress,
|
||||
})
|
||||
}
|
||||
containers = append(containers, pfHelperContainer)
|
||||
@@ -176,6 +181,7 @@ func addHostPorts(withHelperContainer bool, containers []corev1.Container, fwPor
|
||||
for _, fwPort := range fwPorts {
|
||||
if containers[i].Name == fwPort.ContainerName && int(p.ContainerPort) == fwPort.ContainerPort {
|
||||
p.HostPort = int32(fwPort.LocalPort)
|
||||
p.HostIP = customAddress
|
||||
containerPorts = append(containerPorts, p)
|
||||
break
|
||||
}
|
||||
@@ -191,7 +197,10 @@ func getVolumeName(volume string, componentName string, appName string) string {
|
||||
return volume + "-" + componentName + "-" + appName
|
||||
}
|
||||
|
||||
func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool, usedPorts []int, definedPorts []api.ForwardedPort) ([]api.ForwardedPort, error) {
|
||||
func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool, usedPorts []int, definedPorts []api.ForwardedPort, address string) ([]api.ForwardedPort, error) {
|
||||
if address == "" {
|
||||
address = "127.0.0.1"
|
||||
}
|
||||
containerComponents, err := devfileObj.Data.GetComponents(common.DevfileOptions{
|
||||
ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
|
||||
})
|
||||
@@ -268,7 +277,7 @@ func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool,
|
||||
freePort = getCustomLocalPort(ep.TargetPort, containerName)
|
||||
if freePort == 0 {
|
||||
for {
|
||||
freePort, err = util.NextFreePort(startPort, endPort, usedPorts)
|
||||
freePort, err = util.NextFreePort(startPort, endPort, usedPorts, address)
|
||||
if err != nil {
|
||||
klog.Infof("%s", err)
|
||||
continue
|
||||
@@ -290,7 +299,7 @@ func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool,
|
||||
rand.Seed(time.Now().UnixNano()) // #nosec
|
||||
for {
|
||||
freePort = rand.Intn(endPort-startPort+1) + startPort // #nosec
|
||||
if !isPortUsedInContainer(freePort) && util.IsPortFree(freePort) {
|
||||
if !isPortUsedInContainer(freePort) && util.IsPortFree(freePort, address) {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -298,7 +307,7 @@ func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool,
|
||||
}
|
||||
} else {
|
||||
for {
|
||||
freePort, err = util.NextFreePort(startPort, endPort, usedPorts)
|
||||
freePort, err = util.NextFreePort(startPort, endPort, usedPorts, address)
|
||||
if err != nil {
|
||||
klog.Infof("%s", err)
|
||||
continue epLoop
|
||||
@@ -316,7 +325,7 @@ func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool,
|
||||
PortName: portName,
|
||||
IsDebug: isDebugPort,
|
||||
ContainerName: containerName,
|
||||
LocalAddress: "127.0.0.1",
|
||||
LocalAddress: address,
|
||||
LocalPort: freePort,
|
||||
ContainerPort: ep.TargetPort,
|
||||
Exposure: string(ep.Exposure),
|
||||
|
||||
@@ -140,6 +140,7 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
debugCommand string
|
||||
forwardLocalhost bool
|
||||
customForwardedPorts []api.ForwardedPort
|
||||
customAddress string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -310,6 +311,7 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
ContainerPort: 8080,
|
||||
HostPort: 20001,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
return pod
|
||||
},
|
||||
@@ -351,6 +353,7 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
Name: "http",
|
||||
ContainerPort: 20001,
|
||||
HostPort: 20001,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
return pod
|
||||
},
|
||||
@@ -396,6 +399,7 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
ContainerPort: 8080,
|
||||
HostPort: 20001,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
return pod
|
||||
},
|
||||
@@ -441,6 +445,7 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
Name: "http",
|
||||
ContainerPort: 20001,
|
||||
HostPort: 20001,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
return pod
|
||||
},
|
||||
@@ -487,12 +492,14 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
ContainerPort: 8080,
|
||||
HostPort: 20001,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
pod.Spec.Containers[0].Ports = append(pod.Spec.Containers[0].Ports, corev1.ContainerPort{
|
||||
Name: "debug",
|
||||
ContainerPort: 5858,
|
||||
HostPort: 20002,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
return pod
|
||||
},
|
||||
@@ -548,11 +555,13 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
Name: "http",
|
||||
ContainerPort: 20001,
|
||||
HostPort: 20001,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
pod.Spec.Containers[1].Ports = append(pod.Spec.Containers[1].Ports, corev1.ContainerPort{
|
||||
Name: "debug",
|
||||
ContainerPort: 20002,
|
||||
HostPort: 20002,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
return pod
|
||||
},
|
||||
@@ -689,18 +698,21 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
ContainerPort: 20001,
|
||||
HostPort: 20003,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
pod.Spec.Containers[0].Ports = append(pod.Spec.Containers[0].Ports, corev1.ContainerPort{
|
||||
Name: "debug",
|
||||
ContainerPort: 20002,
|
||||
HostPort: 20004,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
pod.Spec.Containers[0].Ports = append(pod.Spec.Containers[0].Ports, corev1.ContainerPort{
|
||||
Name: "debug-1",
|
||||
ContainerPort: 5858,
|
||||
HostPort: 20005,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
return pod
|
||||
},
|
||||
@@ -769,16 +781,19 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
Name: "http",
|
||||
ContainerPort: 20003,
|
||||
HostPort: 20003,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
pod.Spec.Containers[1].Ports = append(pod.Spec.Containers[1].Ports, corev1.ContainerPort{
|
||||
Name: "debug",
|
||||
ContainerPort: 20004,
|
||||
HostPort: 20004,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
pod.Spec.Containers[1].Ports = append(pod.Spec.Containers[1].Ports, corev1.ContainerPort{
|
||||
Name: "debug-1",
|
||||
ContainerPort: 20005,
|
||||
HostPort: 20005,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
return pod
|
||||
},
|
||||
@@ -851,12 +866,14 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
ContainerPort: 20001,
|
||||
HostPort: 20002,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
pod.Spec.Containers[0].Ports = append(pod.Spec.Containers[0].Ports, corev1.ContainerPort{
|
||||
Name: "http-1",
|
||||
ContainerPort: 8080,
|
||||
HostPort: 20003,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
return pod
|
||||
},
|
||||
@@ -920,11 +937,13 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
Name: "http",
|
||||
ContainerPort: 20002,
|
||||
HostPort: 20002,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
pod.Spec.Containers[1].Ports = append(pod.Spec.Containers[1].Ports, corev1.ContainerPort{
|
||||
Name: "http-1",
|
||||
ContainerPort: 20003,
|
||||
HostPort: 20003,
|
||||
HostIP: "127.0.0.1",
|
||||
})
|
||||
return pod
|
||||
},
|
||||
@@ -1002,12 +1021,14 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
ContainerPort: 8080,
|
||||
HostPort: 8080,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
Name: "debug",
|
||||
ContainerPort: 5858,
|
||||
HostPort: 20001,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
},
|
||||
}
|
||||
container2 := pod.Spec.Containers[0].DeepCopy()
|
||||
@@ -1018,6 +1039,7 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
ContainerPort: 5000,
|
||||
HostPort: 20002,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
},
|
||||
}
|
||||
pod.Spec.Containers = append(pod.Spec.Containers, *container2)
|
||||
@@ -1109,11 +1131,13 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
ContainerPort: 8080,
|
||||
HostPort: 8080,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
}, {
|
||||
Name: "debug",
|
||||
ContainerPort: 5858,
|
||||
HostPort: 20001,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
},
|
||||
}
|
||||
container2 := pod.Spec.Containers[0].DeepCopy()
|
||||
@@ -1124,6 +1148,7 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
ContainerPort: 5000,
|
||||
HostPort: 5000,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
},
|
||||
}
|
||||
pod.Spec.Containers = append(pod.Spec.Containers, *container2)
|
||||
@@ -1223,12 +1248,14 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
ContainerPort: 8080,
|
||||
HostPort: 20001,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
Name: "debug",
|
||||
ContainerPort: 5858,
|
||||
HostPort: 20003,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
},
|
||||
}
|
||||
container2 := pod.Spec.Containers[0].DeepCopy()
|
||||
@@ -1239,12 +1266,14 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
ContainerPort: 9000,
|
||||
HostPort: 20002,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
Name: "http-5000",
|
||||
ContainerPort: 5000,
|
||||
HostPort: 5000,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "127.0.0.1",
|
||||
},
|
||||
}
|
||||
pod.Spec.Containers = append(pod.Spec.Containers, *container2)
|
||||
@@ -1289,6 +1318,91 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "basic component + application endpoint + debug endpoint + container ports known - with debug / using customAddress",
|
||||
args: args{
|
||||
devfileObj: func() parser.DevfileObj {
|
||||
data, _ := data.NewDevfileData(string(data.APISchemaVersion200))
|
||||
_ = data.AddCommands([]v1alpha2.Command{command})
|
||||
cmp := baseComponent.DeepCopy()
|
||||
cmp.Container.Endpoints = append(cmp.Container.Endpoints, v1alpha2.Endpoint{
|
||||
Name: "http",
|
||||
TargetPort: 20001,
|
||||
})
|
||||
cmp.Container.Endpoints = append(cmp.Container.Endpoints, v1alpha2.Endpoint{
|
||||
Name: "debug",
|
||||
TargetPort: 20002,
|
||||
})
|
||||
cmp.Container.Endpoints = append(cmp.Container.Endpoints, v1alpha2.Endpoint{
|
||||
Name: "debug-1",
|
||||
TargetPort: 5858,
|
||||
})
|
||||
_ = data.AddComponents([]v1alpha2.Component{*cmp})
|
||||
return parser.DevfileObj{
|
||||
Data: data,
|
||||
}
|
||||
},
|
||||
componentName: devfileName,
|
||||
appName: appName,
|
||||
debug: true,
|
||||
customAddress: "192.168.0.1",
|
||||
},
|
||||
wantPod: func(basePod *corev1.Pod) *corev1.Pod {
|
||||
pod := basePod.DeepCopy()
|
||||
pod.Spec.Containers[0].Ports = append(pod.Spec.Containers[0].Ports, corev1.ContainerPort{
|
||||
Name: "http",
|
||||
ContainerPort: 20001,
|
||||
HostPort: 20003,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "192.168.0.1",
|
||||
})
|
||||
pod.Spec.Containers[0].Ports = append(pod.Spec.Containers[0].Ports, corev1.ContainerPort{
|
||||
Name: "debug",
|
||||
ContainerPort: 20002,
|
||||
HostPort: 20004,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "192.168.0.1",
|
||||
})
|
||||
pod.Spec.Containers[0].Ports = append(pod.Spec.Containers[0].Ports, corev1.ContainerPort{
|
||||
Name: "debug-1",
|
||||
ContainerPort: 5858,
|
||||
HostPort: 20005,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
HostIP: "192.168.0.1",
|
||||
})
|
||||
return pod
|
||||
},
|
||||
wantFwPorts: []api.ForwardedPort{
|
||||
{
|
||||
Platform: "podman",
|
||||
ContainerName: "mycomponent",
|
||||
PortName: "http",
|
||||
LocalAddress: "192.168.0.1",
|
||||
LocalPort: 20003,
|
||||
ContainerPort: 20001,
|
||||
IsDebug: false,
|
||||
},
|
||||
{
|
||||
Platform: "podman",
|
||||
ContainerName: "mycomponent",
|
||||
PortName: "debug",
|
||||
LocalAddress: "192.168.0.1",
|
||||
LocalPort: 20004,
|
||||
ContainerPort: 20002,
|
||||
IsDebug: true,
|
||||
},
|
||||
{
|
||||
Platform: "podman",
|
||||
ContainerName: "mycomponent",
|
||||
PortName: "debug-1",
|
||||
LocalAddress: "192.168.0.1",
|
||||
LocalPort: 20005,
|
||||
ContainerPort: 5858,
|
||||
IsDebug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -1308,6 +1422,7 @@ func Test_createPodFromComponent(t *testing.T) {
|
||||
false,
|
||||
tt.args.customForwardedPorts,
|
||||
[]int{20001, 20002, 20003, 20004, 20005},
|
||||
tt.args.customAddress,
|
||||
)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("createPodFromComponent() error = %v, wantErr %v", err, tt.wantErr)
|
||||
|
||||
@@ -142,7 +142,7 @@ func (o *DevClient) reconcile(
|
||||
|
||||
if options.ForwardLocalhost {
|
||||
// Port-forwarding is enabled by executing dedicated socat commands
|
||||
err = o.portForwardClient.StartPortForwarding(ctx, devfileObj, componentName, options.Debug, options.RandomPorts, options.Out, options.ErrOut, fwPorts)
|
||||
err = o.portForwardClient.StartPortForwarding(ctx, devfileObj, componentName, options.Debug, options.RandomPorts, options.Out, options.ErrOut, fwPorts, options.CustomAddress)
|
||||
if err != nil {
|
||||
return common.NewErrPortForward(err)
|
||||
}
|
||||
@@ -209,6 +209,7 @@ func (o *DevClient) deployPod(ctx context.Context, options dev.StartOptions) (*c
|
||||
options.RandomPorts,
|
||||
options.CustomForwardedPorts,
|
||||
o.usedPorts,
|
||||
options.CustomAddress,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
@@ -119,7 +119,7 @@ type ClientInterface interface {
|
||||
// SetupPortForwarding creates port-forwarding for the pod on the port pairs provided in the
|
||||
// ["<localhost-port>":"<remote-pod-port>"] format. errOut is used by the client-go library to output any errors
|
||||
// encountered while the port-forwarding is running
|
||||
SetupPortForwarding(pod *corev1.Pod, portPairs []string, out io.Writer, errOut io.Writer, stopChan chan struct{}) error
|
||||
SetupPortForwarding(pod *corev1.Pod, portPairs []string, out io.Writer, errOut io.Writer, stopChan chan struct{}, address string) error
|
||||
|
||||
// projects.go
|
||||
CreateNewProject(projectName string, wait bool) error
|
||||
|
||||
@@ -1398,17 +1398,17 @@ func (mr *MockClientInterfaceMockRecorder) SetNamespace(ns interface{}) *gomock.
|
||||
}
|
||||
|
||||
// SetupPortForwarding mocks base method.
|
||||
func (m *MockClientInterface) SetupPortForwarding(pod *v12.Pod, portPairs []string, out, errOut io.Writer, stopChan chan struct{}) error {
|
||||
func (m *MockClientInterface) SetupPortForwarding(pod *v12.Pod, portPairs []string, out, errOut io.Writer, stopChan chan struct{}, address string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetupPortForwarding", pod, portPairs, out, errOut, stopChan)
|
||||
ret := m.ctrl.Call(m, "SetupPortForwarding", pod, portPairs, out, errOut, stopChan, address)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetupPortForwarding indicates an expected call of SetupPortForwarding.
|
||||
func (mr *MockClientInterfaceMockRecorder) SetupPortForwarding(pod, portPairs, out, errOut, stopChan interface{}) *gomock.Call {
|
||||
func (mr *MockClientInterfaceMockRecorder) SetupPortForwarding(pod, portPairs, out, errOut, stopChan, address interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetupPortForwarding", reflect.TypeOf((*MockClientInterface)(nil).SetupPortForwarding), pod, portPairs, out, errOut, stopChan)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetupPortForwarding", reflect.TypeOf((*MockClientInterface)(nil).SetupPortForwarding), pod, portPairs, out, errOut, stopChan, address)
|
||||
}
|
||||
|
||||
// TryWithBlockOwnerDeletion mocks base method.
|
||||
|
||||
@@ -9,7 +9,10 @@ import (
|
||||
"k8s.io/client-go/transport/spdy"
|
||||
)
|
||||
|
||||
func (c *Client) SetupPortForwarding(pod *corev1.Pod, portPairs []string, out io.Writer, errOut io.Writer, stopChan chan struct{}) error {
|
||||
func (c *Client) SetupPortForwarding(pod *corev1.Pod, portPairs []string, out io.Writer, errOut io.Writer, stopChan chan struct{}, address string) error {
|
||||
if address == "" {
|
||||
address = "localhost"
|
||||
}
|
||||
transport, upgrader, err := spdy.RoundTripperFor(c.GetClientConfig())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -20,7 +23,7 @@ func (c *Client) SetupPortForwarding(pod *corev1.Pod, portPairs []string, out io
|
||||
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", req.URL())
|
||||
// passing nil for readyChan because it's eventually being closed if it's not nil
|
||||
// passing nil for out because we only care for error, not for output messages; we want to print our own messages
|
||||
fw, err := portforward.New(dialer, portPairs, stopChan, nil, out, errOut)
|
||||
fw, err := portforward.NewOnAddresses(dialer, []string{address}, portPairs, stopChan, nil, out, errOut)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/klog/v2"
|
||||
ktemplates "k8s.io/kubectl/pkg/util/templates"
|
||||
netutils "k8s.io/utils/net"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/api"
|
||||
"github.com/redhat-developer/odo/pkg/component"
|
||||
@@ -68,6 +69,7 @@ type DevOptions struct {
|
||||
ignoreLocalhostFlag bool
|
||||
forwardLocalhostFlag bool
|
||||
portForwardFlag []string
|
||||
addressFlag string
|
||||
}
|
||||
|
||||
var _ genericclioptions.Runnable = (*DevOptions)(nil)
|
||||
@@ -144,7 +146,12 @@ func (o *DevOptions) Validate(ctx context.Context) error {
|
||||
if o.randomPortsFlag && o.portForwardFlag != nil {
|
||||
return errors.New("--random-ports and --port-forward cannot be used together")
|
||||
}
|
||||
|
||||
// Validate the custom address and return an error (if any) early on, if we do not validate here, it will only throw an error at the stage of port forwarding.
|
||||
if o.addressFlag != "" {
|
||||
if err := validateCustomAddress(o.addressFlag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if o.portForwardFlag != nil {
|
||||
containerEndpointMapping, err := libdevfile.GetDevfileContainerEndpointMapping(devfileObj, true)
|
||||
if err != nil {
|
||||
@@ -157,7 +164,7 @@ func (o *DevOptions) Validate(ctx context.Context) error {
|
||||
}
|
||||
o.forwardedPorts = forwardedPorts
|
||||
|
||||
errStrings, err := validatePortForwardFlagData(forwardedPorts, containerEndpointMapping)
|
||||
errStrings, err := validatePortForwardFlagData(forwardedPorts, containerEndpointMapping, o.addressFlag)
|
||||
if len(errStrings) != 0 {
|
||||
log.Error("There are following issues with values provided by --port-forward flag:")
|
||||
for _, errStr := range errStrings {
|
||||
@@ -249,6 +256,7 @@ func (o *DevOptions) Run(ctx context.Context) (err error) {
|
||||
ForwardLocalhost: o.forwardLocalhostFlag,
|
||||
Variables: variables,
|
||||
CustomForwardedPorts: o.forwardedPorts,
|
||||
CustomAddress: o.addressFlag,
|
||||
Out: o.out,
|
||||
ErrOut: o.errOut,
|
||||
},
|
||||
@@ -299,8 +307,8 @@ It forwards endpoints with any exposure values ('public', 'internal' or 'none')
|
||||
devCmd.Flags().BoolVar(&o.forwardLocalhostFlag, "forward-localhost", false,
|
||||
"Whether to enable port-forwarding if app is listening on the container loopback interface. Applicable only if platform is podman.")
|
||||
devCmd.Flags().StringArrayVar(&o.portForwardFlag, "port-forward", nil,
|
||||
"Define custom port mapping for port forwarding. Acceptable formats: LOCAL_PORT:REMOTE_PORT, LOCAL_PORT:CONTAINER_NAME:REMOTE_PORT. Currently, it is applicable only if platform is cluster.")
|
||||
|
||||
"Define custom port mapping for port forwarding. Acceptable formats: LOCAL_PORT:REMOTE_PORT, LOCAL_PORT:CONTAINER_NAME:REMOTE_PORT.")
|
||||
devCmd.Flags().StringVar(&o.addressFlag, "address", "127.0.0.1", "Define custom address for port forwarding.")
|
||||
clientset.Add(devCmd,
|
||||
clientset.BINDING,
|
||||
clientset.DEV,
|
||||
@@ -327,7 +335,7 @@ It forwards endpoints with any exposure values ('public', 'internal' or 'none')
|
||||
// 1. Every container port defined by the flag is present in the devfile
|
||||
// 2. Every local port defined by the flag is unique
|
||||
// 3. If multiple containers have the same container port, the validation fails and asks the user to provide container names
|
||||
func validatePortForwardFlagData(forwardedPorts []api.ForwardedPort, containerEndpointMapping map[string][]v1alpha2.Endpoint) ([]string, error) {
|
||||
func validatePortForwardFlagData(forwardedPorts []api.ForwardedPort, containerEndpointMapping map[string][]v1alpha2.Endpoint, address string) ([]string, error) {
|
||||
var errors []string
|
||||
// Validate that local ports present in forwardedPorts are unique
|
||||
var localPorts = make(map[int]struct{})
|
||||
@@ -349,7 +357,9 @@ func validatePortForwardFlagData(forwardedPorts []api.ForwardedPort, containerEn
|
||||
portContainerMapping[endpoint.TargetPort] = append(portContainerMapping[endpoint.TargetPort], container)
|
||||
}
|
||||
}
|
||||
|
||||
if address == "" {
|
||||
address = "127.0.0.1"
|
||||
}
|
||||
// Check that all container ports are valid and present in the Devfile
|
||||
portLoop:
|
||||
for _, fPort := range forwardedPorts {
|
||||
@@ -388,6 +398,12 @@ portLoop:
|
||||
errors = append(errors, fmt.Sprintf("container port %d not found in the devfile container endpoints", fPort.ContainerPort))
|
||||
}
|
||||
}
|
||||
for _, fPort := range forwardedPorts {
|
||||
if !util.IsPortFree(fPort.LocalPort, address) {
|
||||
errors = append(errors, fmt.Sprintf("local port %d is already in use on address %s", fPort.LocalPort, address))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) != 0 {
|
||||
return errors, fmt.Errorf("values for --port-forward flag are invalid")
|
||||
}
|
||||
@@ -424,3 +440,15 @@ func parsePortForwardFlag(portForwardFlag []string) (forwardedPorts []api.Forwar
|
||||
}
|
||||
return forwardedPorts, nil
|
||||
}
|
||||
|
||||
// validateCustomAddress validates if the provided ip address is valid;
|
||||
// it uses the same checks as defined by func parseAddresses() in "k8s.io/client-go/tools/portforward"
|
||||
func validateCustomAddress(address string) error {
|
||||
if address == "localhost" {
|
||||
return nil
|
||||
}
|
||||
if netutils.ParseIPSloppy(address).To4() != nil || netutils.ParseIPSloppy(address) != nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%s is an invalid ip address", address)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
package dev
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/redhat-developer/odo/pkg/api"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_validatePortForwardFlagData(t *testing.T) {
|
||||
type serverCloser interface {
|
||||
Close()
|
||||
}
|
||||
type args struct {
|
||||
forwardedPorts []api.ForwardedPort
|
||||
containerEndpointMapping map[string][]v1alpha2.Endpoint
|
||||
address string
|
||||
setupServerFunc func(address string) (serverCloser, error)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -160,10 +169,45 @@ func Test_validatePortForwardFlagData(t *testing.T) {
|
||||
wantErr: true,
|
||||
wantErrStrings: []string{"container port 3000 does not match any endpoints of container \"runtime\" in the devfile", "multiple container components (runtime, tools) found with same container port 5858 in the devfile, port forwarding must be defined with format <localPort>:<containerName>:<containerPort>", "container \"invisible\" not found in the devfile", "local port 9000 is used more than once, please use unique local ports"},
|
||||
},
|
||||
{
|
||||
name: "local port is busy",
|
||||
args: args{
|
||||
forwardedPorts: []api.ForwardedPort{
|
||||
{LocalPort: 9000, ContainerPort: 9000},
|
||||
},
|
||||
containerEndpointMapping: map[string][]v1alpha2.Endpoint{
|
||||
"runtime": {{TargetPort: 9000}},
|
||||
},
|
||||
setupServerFunc: func(address string) (serverCloser, error) {
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("%s:9000", address))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &httptest.Server{
|
||||
Listener: l,
|
||||
Config: &http.Server{},
|
||||
}
|
||||
s.Start()
|
||||
|
||||
return s, nil
|
||||
},
|
||||
address: "localhost",
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrStrings: []string{"local port 9000 is already in use on address localhost"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
errStrings, err := validatePortForwardFlagData(tt.args.forwardedPorts, tt.args.containerEndpointMapping)
|
||||
if tt.args.setupServerFunc != nil {
|
||||
sCloser, err := tt.args.setupServerFunc(tt.args.address)
|
||||
if err != nil {
|
||||
t.Errorf("failed to setup server: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer sCloser.Close()
|
||||
}
|
||||
errStrings, err := validatePortForwardFlagData(tt.args.forwardedPorts, tt.args.containerEndpointMapping, tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validatePortForwardFlagData() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
@@ -285,3 +329,72 @@ func Test_parsePortForwardFlag(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateCustomAddress(t *testing.T) {
|
||||
type args struct {
|
||||
address string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
{
|
||||
name: "--address=localhost",
|
||||
args: args{
|
||||
address: "localhost",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "--address is a valid IPv4",
|
||||
args: args{
|
||||
address: "192.168.0.1",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "--address=0.0.0.0",
|
||||
args: args{
|
||||
address: "0.0.0.0",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "--address is a valid IPv6 address",
|
||||
args: args{
|
||||
address: "ABCD:EF01:2345:6789:ABCD:EF01:2345:6789",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "--address=::1",
|
||||
args: args{
|
||||
address: "::1",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "--address is not a valid address",
|
||||
args: args{
|
||||
address: "e9:9e:06:ee:8c:4c",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "--address=something",
|
||||
args: args{
|
||||
address: "something",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := validateCustomAddress(tt.args.address); (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateCustomAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ type Client interface {
|
||||
out io.Writer,
|
||||
errOut io.Writer,
|
||||
definedPorts []api.ForwardedPort,
|
||||
customAddress string,
|
||||
) error
|
||||
|
||||
// StopPortForwarding stops the port forwarding for the specified component.
|
||||
|
||||
@@ -50,7 +50,7 @@ func NewPFClient(kubernetesClient kclient.ClientInterface, stateClient state.Cli
|
||||
}
|
||||
}
|
||||
|
||||
func (o *PFClient) StartPortForwarding(ctx context.Context, devFileObj parser.DevfileObj, componentName string, debug bool, randomPorts bool, out io.Writer, errOut io.Writer, definedPorts []api.ForwardedPort) error {
|
||||
func (o *PFClient) StartPortForwarding(ctx context.Context, devFileObj parser.DevfileObj, componentName string, debug bool, randomPorts bool, out io.Writer, errOut io.Writer, definedPorts []api.ForwardedPort, customAddress string) error {
|
||||
if randomPorts && len(definedPorts) != 0 {
|
||||
return errors.New("cannot use randomPorts and custom definePorts together")
|
||||
}
|
||||
@@ -75,11 +75,11 @@ func (o *PFClient) StartPortForwarding(ctx context.Context, devFileObj parser.De
|
||||
|
||||
var portPairs map[string][]string
|
||||
if len(definedPorts) != 0 {
|
||||
portPairs = getCustomPortPairs(definedPorts, ceMapping)
|
||||
portPairs = getCustomPortPairs(definedPorts, ceMapping, customAddress)
|
||||
} else if randomPorts {
|
||||
portPairs = randomPortPairsFromContainerEndpoints(ceMapping)
|
||||
} else {
|
||||
portPairs = portPairsFromContainerEndpoints(ceMapping)
|
||||
portPairs = portPairsFromContainerEndpoints(ceMapping, customAddress)
|
||||
}
|
||||
var portPairsSlice []string
|
||||
for _, v1 := range portPairs {
|
||||
@@ -111,7 +111,7 @@ func (o *PFClient) StartPortForwarding(ctx context.Context, devFileObj parser.De
|
||||
backo := watch.NewExpBackoff()
|
||||
for {
|
||||
o.finishedChan = make(chan struct{}, 1)
|
||||
portsBuf := NewPortWriter(log.GetStdout(), len(portPairsSlice), ceMapping)
|
||||
portsBuf := NewPortWriter(log.GetStdout(), len(portPairsSlice), ceMapping, customAddress)
|
||||
|
||||
go func() {
|
||||
portsBuf.Wait()
|
||||
@@ -122,7 +122,7 @@ func (o *PFClient) StartPortForwarding(ctx context.Context, devFileObj parser.De
|
||||
devstateChan <- err
|
||||
}()
|
||||
|
||||
err = o.kubernetesClient.SetupPortForwarding(pod, portPairsSlice, portsBuf, errOut, o.stopChan)
|
||||
err = o.kubernetesClient.SetupPortForwarding(pod, portPairsSlice, portsBuf, errOut, o.stopChan, customAddress)
|
||||
if err != nil {
|
||||
fmt.Fprintf(errOut, "Failed to setup port-forwarding: %v\n", err)
|
||||
d := backo.Delay()
|
||||
@@ -171,7 +171,7 @@ func (o *PFClient) GetForwardedPorts() map[string][]v1alpha2.Endpoint {
|
||||
|
||||
// getCustomPortPairs assigns custom port on localhost to a container port if provided by the definedPorts config,
|
||||
// if not, it assigns a port starting from 20001 as done in portPairsFromContainerEndpoints
|
||||
func getCustomPortPairs(definedPorts []api.ForwardedPort, ceMapping map[string][]v1alpha2.Endpoint) map[string][]string {
|
||||
func getCustomPortPairs(definedPorts []api.ForwardedPort, ceMapping map[string][]v1alpha2.Endpoint, address string) map[string][]string {
|
||||
portPairs := make(map[string][]string)
|
||||
usedPorts := make(map[int]struct{})
|
||||
for _, dPort := range definedPorts {
|
||||
@@ -210,7 +210,7 @@ func getCustomPortPairs(definedPorts []api.ForwardedPort, ceMapping map[string][
|
||||
if freePort == 0 {
|
||||
for {
|
||||
var err error
|
||||
freePort, err = util.NextFreePort(startPort, endPort, nil)
|
||||
freePort, err = util.NextFreePort(startPort, endPort, nil, address)
|
||||
if err != nil {
|
||||
klog.Infof("%s", err)
|
||||
continue
|
||||
@@ -250,13 +250,13 @@ func randomPortPairsFromContainerEndpoints(ceMap map[string][]v1alpha2.Endpoint)
|
||||
// portPairsFromContainerEndpoints assigns a port on localhost to each port in the provided containerEndpoints map
|
||||
// it returns a map of the format "<container-name>":{"<local-port-1>:<remote-port-1>", "<local-port-2>:<remote-port-2>"}
|
||||
// "container1": {"20001:3000", "20002:3001"}
|
||||
func portPairsFromContainerEndpoints(ceMap map[string][]v1alpha2.Endpoint) map[string][]string {
|
||||
func portPairsFromContainerEndpoints(ceMap map[string][]v1alpha2.Endpoint, address string) map[string][]string {
|
||||
portPairs := make(map[string][]string)
|
||||
startPort := 20001
|
||||
endPort := startPort + 10000
|
||||
for name, ports := range ceMap {
|
||||
for _, p := range ports {
|
||||
freePort, err := util.NextFreePort(startPort, endPort, nil)
|
||||
freePort, err := util.NextFreePort(startPort, endPort, nil, address)
|
||||
if err != nil {
|
||||
klog.Infof("%s", err)
|
||||
continue
|
||||
|
||||
@@ -72,7 +72,7 @@ func Test_getCompleteCustomPortPairs(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotPortPairs := getCustomPortPairs(tt.args.definedPorts, tt.args.ceMapping)
|
||||
gotPortPairs := getCustomPortPairs(tt.args.definedPorts, tt.args.ceMapping, "")
|
||||
if diff := cmp.Diff(gotPortPairs, tt.wantPortPairs); diff != "" {
|
||||
t.Errorf("getCompleteCustomPortPairs() (got vs want) diff = %v", diff)
|
||||
}
|
||||
|
||||
@@ -23,27 +23,32 @@ type PortWriter struct {
|
||||
end chan bool
|
||||
len int
|
||||
// mapping indicates the list of endpoints open by containers
|
||||
mapping map[string][]v1alpha2.Endpoint
|
||||
fwPorts []api.ForwardedPort
|
||||
mapping map[string][]v1alpha2.Endpoint
|
||||
fwPorts []api.ForwardedPort
|
||||
customAddress string
|
||||
}
|
||||
|
||||
// NewPortWriter creates a writer that will write the content in buffer,
|
||||
// and Wait will return after strings "Forwarding from 127.0.0.1:" has been written "len" times
|
||||
func NewPortWriter(buffer io.Writer, len int, mapping map[string][]v1alpha2.Endpoint) *PortWriter {
|
||||
func NewPortWriter(buffer io.Writer, len int, mapping map[string][]v1alpha2.Endpoint, customAddress string) *PortWriter {
|
||||
return &PortWriter{
|
||||
buffer: buffer,
|
||||
len: len,
|
||||
end: make(chan bool),
|
||||
mapping: mapping,
|
||||
buffer: buffer,
|
||||
len: len,
|
||||
end: make(chan bool),
|
||||
mapping: mapping,
|
||||
customAddress: customAddress,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *PortWriter) Write(buf []byte) (n int, err error) {
|
||||
|
||||
if o.customAddress == "" {
|
||||
o.customAddress = "127.0.0.1"
|
||||
}
|
||||
s := string(buf)
|
||||
if strings.HasPrefix(s, "Forwarding from 127.0.0.1") {
|
||||
if strings.HasPrefix(s, fmt.Sprintf("Forwarding from %s", o.customAddress)) {
|
||||
|
||||
fwPort, err := getForwardedPort(o.mapping, s)
|
||||
fwPort, err := getForwardedPort(o.mapping, s, o.customAddress)
|
||||
if err == nil {
|
||||
o.fwPorts = append(o.fwPorts, fwPort)
|
||||
} else {
|
||||
@@ -68,8 +73,11 @@ func (o *PortWriter) GetForwardedPorts() []api.ForwardedPort {
|
||||
return o.fwPorts
|
||||
}
|
||||
|
||||
func getForwardedPort(mapping map[string][]v1alpha2.Endpoint, s string) (api.ForwardedPort, error) {
|
||||
regex := regexp.MustCompile(`Forwarding from 127.0.0.1:([0-9]+) -> ([0-9]+)`)
|
||||
func getForwardedPort(mapping map[string][]v1alpha2.Endpoint, s string, address string) (api.ForwardedPort, error) {
|
||||
if address == "" {
|
||||
address = "127.0.0.1"
|
||||
}
|
||||
regex := regexp.MustCompile(fmt.Sprintf(`Forwarding from %s:([0-9]+) -> ([0-9]+)`, address))
|
||||
matches := regex.FindStringSubmatch(s)
|
||||
if len(matches) < 3 {
|
||||
return api.ForwardedPort{}, errors.New("unable to analyze port forwarding string")
|
||||
@@ -83,7 +91,7 @@ func getForwardedPort(mapping map[string][]v1alpha2.Endpoint, s string) (api.For
|
||||
return api.ForwardedPort{}, err
|
||||
}
|
||||
fp := api.ForwardedPort{
|
||||
LocalAddress: "127.0.0.1",
|
||||
LocalAddress: address,
|
||||
LocalPort: localPort,
|
||||
ContainerPort: remotePort,
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ func Test_getForwardedPort(t *testing.T) {
|
||||
type args struct {
|
||||
mapping map[string][]v1alpha2.Endpoint
|
||||
s string
|
||||
address string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -45,6 +46,32 @@ func Test_getForwardedPort(t *testing.T) {
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "find port in container and use custom address",
|
||||
args: args{
|
||||
mapping: map[string][]v1alpha2.Endpoint{
|
||||
"container1": {
|
||||
v1alpha2.Endpoint{Name: "port-11", TargetPort: 3000},
|
||||
v1alpha2.Endpoint{Name: "debug-11", TargetPort: 4200},
|
||||
},
|
||||
"container2": {
|
||||
v1alpha2.Endpoint{Name: "port-21", TargetPort: 80},
|
||||
v1alpha2.Endpoint{Name: "port-22", TargetPort: 8080},
|
||||
},
|
||||
},
|
||||
s: "Forwarding from 192.168.0.1:40407 -> 3000",
|
||||
address: "192.168.0.1",
|
||||
},
|
||||
want: api.ForwardedPort{
|
||||
ContainerName: "container1",
|
||||
PortName: "port-11",
|
||||
LocalAddress: "192.168.0.1",
|
||||
IsDebug: false,
|
||||
LocalPort: 40407,
|
||||
ContainerPort: 3000,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "string error",
|
||||
args: args{
|
||||
@@ -91,7 +118,7 @@ func Test_getForwardedPort(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := getForwardedPort(tt.args.mapping, tt.args.s)
|
||||
got, err := getForwardedPort(tt.args.mapping, tt.args.s, tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("getForwardedPort() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
||||
@@ -45,6 +45,7 @@ func (o *PFClient) StartPortForwarding(
|
||||
out io.Writer,
|
||||
errOut io.Writer,
|
||||
definedPorts []api.ForwardedPort,
|
||||
customAddress string,
|
||||
) error {
|
||||
var appliedPorts []api.ForwardedPort
|
||||
for port := range o.appliedPorts {
|
||||
|
||||
@@ -781,9 +781,12 @@ func SafeGetBool(b *bool) bool {
|
||||
return *b
|
||||
}
|
||||
|
||||
// IsPortFree checks if the port on localhost is free to use
|
||||
func IsPortFree(port int) bool {
|
||||
address := fmt.Sprintf("0.0.0.0:%d", port)
|
||||
// IsPortFree checks if the port on a given address is free to use
|
||||
func IsPortFree(port int, localAddress string) bool {
|
||||
if localAddress == "" {
|
||||
localAddress = "127.0.0.1"
|
||||
}
|
||||
address := fmt.Sprintf("%s:%d", localAddress, port)
|
||||
listener, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -795,7 +798,8 @@ func IsPortFree(port int) bool {
|
||||
// NextFreePort returns the next free port on system, starting at start
|
||||
// end finishing at end.
|
||||
// If no port is found in the range [start, end], 0 is returned
|
||||
func NextFreePort(start, end int, usedPorts []int) (int, error) {
|
||||
func NextFreePort(start, end int, usedPorts []int, address string) (int, error) {
|
||||
|
||||
port := start
|
||||
for {
|
||||
for _, usedPort := range usedPorts {
|
||||
@@ -803,7 +807,7 @@ func NextFreePort(start, end int, usedPorts []int) (int, error) {
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
if IsPortFree(port) {
|
||||
if IsPortFree(port, address) {
|
||||
return port, nil
|
||||
}
|
||||
port++
|
||||
|
||||
@@ -2742,7 +2742,8 @@ func TestIsPortFree(t *testing.T) {
|
||||
}
|
||||
type args struct {
|
||||
port int
|
||||
portProvider func() (int, serverCloser, error)
|
||||
portProvider func(address string) (int, serverCloser, error)
|
||||
address string
|
||||
}
|
||||
type test struct {
|
||||
name string
|
||||
@@ -2763,7 +2764,7 @@ func TestIsPortFree(t *testing.T) {
|
||||
{
|
||||
name: "random port bound on 127.0.0.1",
|
||||
args: args{
|
||||
portProvider: func() (int, serverCloser, error) {
|
||||
portProvider: func(address string) (int, serverCloser, error) {
|
||||
s := httptest.NewServer(nil)
|
||||
_, p, err := net.SplitHostPort(strings.TrimPrefix(s.URL, "http://"))
|
||||
if err != nil {
|
||||
@@ -2781,7 +2782,7 @@ func TestIsPortFree(t *testing.T) {
|
||||
{
|
||||
name: "random port bound on 127.0.0.1 and checking 0 as input",
|
||||
args: args{
|
||||
portProvider: func() (int, serverCloser, error) {
|
||||
portProvider: func(address string) (int, serverCloser, error) {
|
||||
s := httptest.NewServer(nil)
|
||||
return 0, s, nil
|
||||
},
|
||||
@@ -2791,9 +2792,9 @@ func TestIsPortFree(t *testing.T) {
|
||||
{
|
||||
name: "random port bound on 0.0.0.0 and checking 0 as input",
|
||||
args: args{
|
||||
portProvider: func() (int, serverCloser, error) {
|
||||
portProvider: func(address string) (int, serverCloser, error) {
|
||||
// Intentionally not using httptest.Server, which listens to 127.0.0.1
|
||||
l, err := net.Listen("tcp", "0.0.0.0:0")
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("%s:0", address))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
@@ -2805,15 +2806,16 @@ func TestIsPortFree(t *testing.T) {
|
||||
|
||||
return 0, s, nil
|
||||
},
|
||||
address: "0.0.0.0",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "random port bound on 0.0.0.0",
|
||||
args: args{
|
||||
portProvider: func() (int, serverCloser, error) {
|
||||
portProvider: func(address string) (int, serverCloser, error) {
|
||||
// Intentionally not using httptest.Server, which listens to 127.0.0.1
|
||||
l, err := net.Listen("tcp", "0.0.0.0:0")
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("%s:0", address))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
@@ -2833,6 +2835,7 @@ func TestIsPortFree(t *testing.T) {
|
||||
}
|
||||
return port, s, nil
|
||||
},
|
||||
address: "0.0.0.0",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
@@ -2844,7 +2847,7 @@ func TestIsPortFree(t *testing.T) {
|
||||
var s serverCloser
|
||||
var err error
|
||||
if tt.args.portProvider != nil {
|
||||
port, s, err = tt.args.portProvider()
|
||||
port, s, err = tt.args.portProvider(tt.args.address)
|
||||
if s != nil {
|
||||
defer s.Close()
|
||||
}
|
||||
@@ -2854,7 +2857,7 @@ func TestIsPortFree(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if got := IsPortFree(port); got != tt.want {
|
||||
if got := IsPortFree(port, tt.args.address); got != tt.want {
|
||||
t.Errorf("IsPortFree() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
|
||||
56
tests/examples/source/devfiles/go-devfiles/devfile.yaml
Normal file
56
tests/examples/source/devfiles/go-devfiles/devfile.yaml
Normal file
@@ -0,0 +1,56 @@
|
||||
commands:
|
||||
- exec:
|
||||
commandLine: go build main.go
|
||||
component: runtime
|
||||
env:
|
||||
- name: GOPATH
|
||||
value: ${PROJECT_SOURCE}/.go
|
||||
- name: GOCACHE
|
||||
value: ${PROJECT_SOURCE}/.cache
|
||||
group:
|
||||
isDefault: true
|
||||
kind: build
|
||||
workingDir: ${PROJECT_SOURCE}
|
||||
id: build
|
||||
- exec:
|
||||
commandLine: ./main
|
||||
component: runtime
|
||||
group:
|
||||
isDefault: true
|
||||
kind: run
|
||||
workingDir: ${PROJECT_SOURCE}
|
||||
id: run
|
||||
components:
|
||||
- container:
|
||||
args:
|
||||
- tail
|
||||
- -f
|
||||
- /dev/null
|
||||
endpoints:
|
||||
- name: http-go
|
||||
targetPort: 8080
|
||||
image: registry.access.redhat.com/ubi9/go-toolset:1.18.9-14
|
||||
memoryLimit: 1024Mi
|
||||
mountSources: true
|
||||
name: runtime
|
||||
metadata:
|
||||
description: Go (version 1.18.x) is an open source programming language that makes
|
||||
it easy to build simple, reliable, and efficient software.
|
||||
displayName: Go Runtime
|
||||
icon: https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg
|
||||
language: Go
|
||||
name: my-go-app
|
||||
projectType: Go
|
||||
provider: Red Hat
|
||||
tags:
|
||||
- Go
|
||||
version: 1.0.2
|
||||
schemaVersion: 2.1.0
|
||||
starterProjects:
|
||||
- description: A Go project with a simple HTTP server
|
||||
git:
|
||||
checkoutFrom:
|
||||
revision: main
|
||||
remotes:
|
||||
origin: https://github.com/devfile-samples/devfile-stack-go.git
|
||||
name: go-starter
|
||||
@@ -1,6 +1,7 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
@@ -112,6 +113,7 @@ type DevSession struct {
|
||||
session *gexec.Session
|
||||
stopped bool
|
||||
console *expect.Console
|
||||
address string
|
||||
}
|
||||
|
||||
type DevSessionOpts struct {
|
||||
@@ -121,6 +123,7 @@ type DevSessionOpts struct {
|
||||
TimeoutInSeconds int
|
||||
NoRandomPorts bool
|
||||
NoWatch bool
|
||||
CustomAddress string
|
||||
}
|
||||
|
||||
// StartDevMode starts a dev session with `odo dev`
|
||||
@@ -143,6 +146,9 @@ func StartDevMode(options DevSessionOpts) (devSession DevSession, out []byte, er
|
||||
if options.NoWatch {
|
||||
args = append(args, "--no-watch")
|
||||
}
|
||||
if options.CustomAddress != "" {
|
||||
args = append(args, "--address", options.CustomAddress)
|
||||
}
|
||||
args = append(args, options.CmdlineArgs...)
|
||||
cmd := Cmd("odo", args...)
|
||||
cmd.Cmd.Stdin = c.Tty()
|
||||
@@ -158,6 +164,7 @@ func StartDevMode(options DevSessionOpts) (devSession DevSession, out []byte, er
|
||||
result := DevSession{
|
||||
session: session,
|
||||
console: c,
|
||||
address: options.CustomAddress,
|
||||
}
|
||||
outContents := session.Out.Contents()
|
||||
errContents := session.Err.Contents()
|
||||
@@ -169,7 +176,7 @@ func StartDevMode(options DevSessionOpts) (devSession DevSession, out []byte, er
|
||||
if err != nil {
|
||||
return DevSession{}, nil, nil, nil, err
|
||||
}
|
||||
return result, outContents, errContents, getPorts(string(outContents)), nil
|
||||
return result, outContents, errContents, getPorts(string(outContents), options.CustomAddress), nil
|
||||
|
||||
}
|
||||
|
||||
@@ -246,7 +253,7 @@ func (o DevSession) GetInfo() ([]byte, []byte, map[string]string, error) {
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return outContents, errContents, getPorts(string(outContents)), nil
|
||||
return outContents, errContents, getPorts(string(outContents), o.address), nil
|
||||
}
|
||||
|
||||
func (o DevSession) CheckNotSynced(timeout time.Duration) {
|
||||
@@ -283,6 +290,9 @@ func WaitForDevModeToContain(options DevSessionOpts, substring string, stopSessi
|
||||
if options.RunOnPodman {
|
||||
args = append(args, "--platform", "podman")
|
||||
}
|
||||
if options.CustomAddress != "" {
|
||||
args = append(args, "--address", options.CustomAddress)
|
||||
}
|
||||
session := Cmd("odo", args...).AddEnv(options.EnvVars...).Runner().session
|
||||
if checkErrOut {
|
||||
WaitForErroutToContain(substring, 360, 10, session)
|
||||
@@ -291,6 +301,7 @@ func WaitForDevModeToContain(options DevSessionOpts, substring string, stopSessi
|
||||
}
|
||||
result := DevSession{
|
||||
session: session,
|
||||
address: options.CustomAddress,
|
||||
}
|
||||
if stopSessionAfter {
|
||||
defer func() {
|
||||
@@ -315,9 +326,12 @@ func WaitForDevModeToContain(options DevSessionOpts, substring string, stopSessi
|
||||
// getPorts returns a map of ports redirected depending on the information in s
|
||||
//
|
||||
// `- Forwarding from 127.0.0.1:20001 -> 3000` will return { "3000": "127.0.0.1:20001" }
|
||||
func getPorts(s string) map[string]string {
|
||||
func getPorts(s, address string) map[string]string {
|
||||
if address == "" {
|
||||
address = "127.0.0.1"
|
||||
}
|
||||
result := map[string]string{}
|
||||
re := regexp.MustCompile("(127.0.0.1:[0-9]+) -> ([0-9]+)")
|
||||
re := regexp.MustCompile(fmt.Sprintf("(%s:[0-9]+) -> ([0-9]+)", address))
|
||||
matches := re.FindAllStringSubmatch(s, -1)
|
||||
for _, match := range matches {
|
||||
result[match[2]] = match[1]
|
||||
|
||||
@@ -78,7 +78,7 @@ var _ = Describe("odo dev debug command tests", func() {
|
||||
|
||||
It("should connect to relevant custom ports forwarded", func() {
|
||||
By("connecting to the application port", func() {
|
||||
helper.HttpWaitForWithStatus("http://"+ports[ContainerPort], "Hello from Node.js Starter Application!", 12, 5, 200)
|
||||
helper.HttpWaitForWithStatus(fmt.Sprintf("http://%s", ports[ContainerPort]), "Hello from Node.js Starter Application!", 12, 5, 200)
|
||||
})
|
||||
By("expecting a ws connection when tried to connect on default debug port locally", func() {
|
||||
// 400 response expected because the endpoint expects a websocket request and we are doing a HTTP GET
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -936,6 +937,111 @@ ComponentSettings:
|
||||
})
|
||||
}
|
||||
|
||||
for _, podman := range []bool{true, false} {
|
||||
podman := podman
|
||||
Context(fmt.Sprintf("multiple dev sessions with different project are running on same platform (podman=%v), same port", podman), helper.LabelPodmanIf(podman, func() {
|
||||
const (
|
||||
nodejsContainerPort = "3000"
|
||||
goContainerPort = "8080"
|
||||
nodejsCustomAddress = "127.0.10.3"
|
||||
goCustomAddress = "127.0.10.1"
|
||||
)
|
||||
var (
|
||||
nodejsProject, goProject string
|
||||
nodejsDevSession, goDevSession helper.DevSession
|
||||
nodejsPorts, goPorts map[string]string
|
||||
nodejsLocalPort = helper.GetCustomStartPort()
|
||||
goLocalPort = nodejsLocalPort + 1
|
||||
|
||||
nodejsURL = fmt.Sprintf("%s:%d", nodejsCustomAddress, nodejsLocalPort)
|
||||
goURL = fmt.Sprintf("%s:%d", goCustomAddress, goLocalPort)
|
||||
)
|
||||
BeforeEach(func() {
|
||||
if runtime.GOOS == "darwin" {
|
||||
Skip("cannot run this test out of the box on macOS because the test uses a custom address in the range 127.0.0/8 and for macOS we need to ensure the addresses are open for request before using them; Ref: https://superuser.com/questions/458875/how-do-you-get-loopback-addresses-other-than-127-0-0-1-to-work-on-os-x#458877")
|
||||
}
|
||||
nodejsProject = helper.CreateNewContext()
|
||||
goProject = helper.CreateNewContext()
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), nodejsProject)
|
||||
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(nodejsProject, "devfile.yaml"))
|
||||
helper.CopyExample(filepath.Join("source", "go"), goProject)
|
||||
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "go-devfiles", "devfile.yaml"), filepath.Join(goProject, "devfile.yaml"))
|
||||
})
|
||||
AfterEach(func() {
|
||||
helper.DeleteDir(nodejsProject)
|
||||
helper.DeleteDir(goProject)
|
||||
})
|
||||
When("odo dev session is run for nodejs component", func() {
|
||||
BeforeEach(func() {
|
||||
helper.Chdir(nodejsProject)
|
||||
var err error
|
||||
nodejsDevSession, _, _, nodejsPorts, err = helper.StartDevMode(helper.DevSessionOpts{
|
||||
CmdlineArgs: []string{"--port-forward", fmt.Sprintf("%d:%s", nodejsLocalPort, nodejsContainerPort)},
|
||||
RunOnPodman: podman,
|
||||
TimeoutInSeconds: 0,
|
||||
NoRandomPorts: true,
|
||||
CustomAddress: nodejsCustomAddress,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
AfterEach(func() {
|
||||
nodejsDevSession.Stop()
|
||||
nodejsDevSession.WaitEnd()
|
||||
helper.Chdir(commonVar.Context)
|
||||
})
|
||||
When("odo dev session is run for go project on the same port but different address", func() {
|
||||
BeforeEach(func() {
|
||||
helper.Chdir(goProject)
|
||||
var err error
|
||||
goDevSession, _, _, goPorts, err = helper.StartDevMode(helper.DevSessionOpts{
|
||||
CmdlineArgs: []string{"--port-forward", fmt.Sprintf("%d:%s", goLocalPort, goContainerPort)},
|
||||
RunOnPodman: podman,
|
||||
TimeoutInSeconds: 0,
|
||||
NoRandomPorts: true,
|
||||
CustomAddress: goCustomAddress,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
AfterEach(func() {
|
||||
goDevSession.Stop()
|
||||
goDevSession.WaitEnd()
|
||||
helper.Chdir(commonVar.Context)
|
||||
})
|
||||
It("should be able to run both the sessions", func() {
|
||||
Expect(nodejsPorts[nodejsContainerPort]).To(BeEquivalentTo(nodejsURL))
|
||||
Expect(goPorts[goContainerPort]).To(BeEquivalentTo(goURL))
|
||||
helper.HttpWaitForWithStatus(fmt.Sprintf("http://%s", nodejsURL), "Hello from Node.js Starter Application!", 1, 0, 200)
|
||||
helper.HttpWaitForWithStatus(fmt.Sprintf("http://%s", goURL), "Hello, !", 1, 0, 200)
|
||||
})
|
||||
When("go and nodejs files are modified", func() {
|
||||
BeforeEach(func() {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, _, _, err := nodejsDevSession.WaitSync()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, _, _, err := goDevSession.WaitSync()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}()
|
||||
helper.ReplaceString(filepath.Join(goProject, "main.go"), "Hello, %s!", "H3110, %s!")
|
||||
helper.ReplaceString(filepath.Join(nodejsProject, "server.js"), "Hello from Node.js", "H3110 from Node.js")
|
||||
wg.Wait()
|
||||
})
|
||||
It("should be possible to access both the projects on same address and port", func() {
|
||||
Expect(nodejsPorts[nodejsContainerPort]).To(BeEquivalentTo(nodejsURL))
|
||||
Expect(goPorts[goContainerPort]).To(BeEquivalentTo(goURL))
|
||||
helper.HttpWaitForWithStatus(fmt.Sprintf("http://%s", nodejsURL), "H3110 from Node.js Starter Application!", 1, 0, 200)
|
||||
helper.HttpWaitForWithStatus(fmt.Sprintf("http://%s", goURL), "H3110, !", 1, 0, 200)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}))
|
||||
}
|
||||
for _, podman := range []bool{true, false} {
|
||||
podman := podman
|
||||
Context("port-forwarding for the component", helper.LabelPodmanIf(podman, func() {
|
||||
@@ -976,261 +1082,276 @@ ComponentSettings:
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
for _, customPortForwarding := range []bool{true, false} {
|
||||
customPortForwarding := customPortForwarding
|
||||
var NoRandomPorts bool
|
||||
if customPortForwarding {
|
||||
NoRandomPorts = true
|
||||
for _, customAddress := range []bool{true, false} {
|
||||
customAddress := customAddress
|
||||
var localAddress string
|
||||
if customAddress {
|
||||
localAddress = "0.0.0.0"
|
||||
}
|
||||
When("devfile has single endpoint", func() {
|
||||
var (
|
||||
LocalPort int
|
||||
)
|
||||
const (
|
||||
ContainerPort = "3000"
|
||||
)
|
||||
BeforeEach(func() {
|
||||
LocalPort = helper.GetCustomStartPort()
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
|
||||
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile.yaml")).ShouldPass()
|
||||
for _, customPortForwarding := range []bool{true, false} {
|
||||
customPortForwarding := customPortForwarding
|
||||
var NoRandomPorts bool
|
||||
if customPortForwarding {
|
||||
NoRandomPorts = true
|
||||
}
|
||||
When("devfile has single endpoint", func() {
|
||||
var (
|
||||
localPort int
|
||||
)
|
||||
const (
|
||||
containerPort = "3000"
|
||||
)
|
||||
BeforeEach(func() {
|
||||
localPort = helper.GetCustomStartPort()
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
|
||||
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile.yaml")).ShouldPass()
|
||||
})
|
||||
|
||||
When("running odo dev", func() {
|
||||
var devSession helper.DevSession
|
||||
var ports map[string]string
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
opts := []string{}
|
||||
if customPortForwarding {
|
||||
opts = []string{fmt.Sprintf("--port-forward=%d:%s", localPort, containerPort)}
|
||||
}
|
||||
if manual {
|
||||
opts = append(opts, "--no-watch")
|
||||
}
|
||||
devSession, _, _, ports, err = helper.StartDevMode(helper.DevSessionOpts{
|
||||
CmdlineArgs: opts,
|
||||
NoRandomPorts: NoRandomPorts,
|
||||
RunOnPodman: podman,
|
||||
CustomAddress: localAddress,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
devSession.Stop()
|
||||
devSession.WaitEnd()
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("should expose the endpoint on localhost (podman=%v, manual=%v, customPortForwarding=%v, customAddress=%v)", podman, manual, customPortForwarding, customAddress), func() {
|
||||
url := fmt.Sprintf("http://%s", ports[containerPort])
|
||||
if customPortForwarding {
|
||||
Expect(url).To(ContainSubstring(strconv.Itoa(localPort)))
|
||||
}
|
||||
resp, err := http.Get(url)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
helper.MatchAllInOutput(string(body), []string{"Hello from Node.js Starter Application!"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
When("modifying memoryLimit for container in Devfile", func() {
|
||||
var stdout string
|
||||
var stderr string
|
||||
BeforeEach(func() {
|
||||
if manual {
|
||||
if os.Getenv("SKIP_KEY_PRESS") == "true" {
|
||||
Skip("This is a unix-terminal specific scenario, skipping")
|
||||
}
|
||||
}
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
err error
|
||||
stdoutBytes []byte
|
||||
stderrBytes []byte
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
stdoutBytes, stderrBytes, ports, err = devSession.WaitSync()
|
||||
Expect(err).Should(Succeed())
|
||||
stdout = string(stdoutBytes)
|
||||
stderr = string(stderrBytes)
|
||||
}()
|
||||
src := "memoryLimit: 1024Mi"
|
||||
dst := "memoryLimit: 1023Mi"
|
||||
helper.ReplaceString("devfile.yaml", src, dst)
|
||||
if manual {
|
||||
devSession.PressKey('p')
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("should react on the Devfile modification (podman=%v, manual=%v, customPortForwarding=%v, customAddress=%v)", podman, manual, customPortForwarding, customAddress), func() {
|
||||
if podman {
|
||||
By("warning users that odo dev needs to be restarted", func() {
|
||||
Expect(stdout).To(ContainSubstring(
|
||||
"Detected changes in the Devfile, but this is not supported yet on Podman. Please restart 'odo dev' for such changes to be applied."))
|
||||
})
|
||||
} else {
|
||||
By("not warning users that odo dev needs to be restarted", func() {
|
||||
warning := "Please restart 'odo dev'"
|
||||
Expect(stdout).ShouldNot(ContainSubstring(warning))
|
||||
Expect(stderr).ShouldNot(ContainSubstring(warning))
|
||||
})
|
||||
By("updating the pod", func() {
|
||||
podName := commonVar.CliRunner.GetRunningPodNameByComponent(cmpName, commonVar.Project)
|
||||
bufferOutput := commonVar.CliRunner.Run("get", "pods", podName, "-o", "jsonpath='{.spec.containers[0].resources.requests.memory}'").Out.Contents()
|
||||
output := string(bufferOutput)
|
||||
Expect(output).To(ContainSubstring("1023Mi"))
|
||||
})
|
||||
|
||||
By("exposing the endpoint", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
url := fmt.Sprintf("http://%s", ports[containerPort])
|
||||
if customPortForwarding {
|
||||
Expect(url).To(ContainSubstring(strconv.Itoa(localPort)))
|
||||
}
|
||||
if customAddress {
|
||||
Expect(url).To(ContainSubstring(localAddress))
|
||||
}
|
||||
resp, err := http.Get(url)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
for _, i := range []string{"Hello from Node.js Starter Application!"} {
|
||||
g.Expect(string(body)).To(ContainSubstring(i))
|
||||
}
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
}).WithPolling(1 * time.Second).WithTimeout(20 * time.Second).Should(Succeed())
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When("running odo dev", func() {
|
||||
var devSession helper.DevSession
|
||||
var ports map[string]string
|
||||
When("devfile has multiple endpoints", func() {
|
||||
var (
|
||||
localPort1, localPort2, localPort3 int
|
||||
)
|
||||
const (
|
||||
// ContainerPort<N> are hard-coded from devfile-with-multiple-endpoints.yaml
|
||||
// Note 1: Debug endpoints will not be exposed for this instance, so we do not add custom mapping for them.
|
||||
// Note 2: We add custom mapping for all the endpoints so that none of them are assigned random ports from the 20001-30001 range;
|
||||
// Note 2(contd.): this is to avoid a race condition where a test running in parallel is also assigned similar ranged port the one here, and we fail to access either of them.
|
||||
containerPort1 = "3000"
|
||||
containerPort2 = "4567"
|
||||
containerPort3 = "7890"
|
||||
)
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
opts := []string{}
|
||||
if customPortForwarding {
|
||||
opts = []string{fmt.Sprintf("--port-forward=%d:%s", LocalPort, ContainerPort)}
|
||||
}
|
||||
if manual {
|
||||
opts = append(opts, "--no-watch")
|
||||
}
|
||||
devSession, _, _, ports, err = helper.StartDevMode(helper.DevSessionOpts{
|
||||
CmdlineArgs: opts,
|
||||
NoRandomPorts: NoRandomPorts,
|
||||
RunOnPodman: podman,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
localPort1 = helper.GetCustomStartPort()
|
||||
localPort2 = localPort1 + 1
|
||||
localPort3 = localPort1 + 2
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project-with-multiple-endpoints"), commonVar.Context)
|
||||
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-with-multiple-endpoints.yaml")).ShouldPass()
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
devSession.Stop()
|
||||
devSession.WaitEnd()
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("should expose the endpoint on localhost (podman=%v, manual=%v, customPortForwarding=%v)", podman, manual, customPortForwarding), func() {
|
||||
url := fmt.Sprintf("http://%s", ports[ContainerPort])
|
||||
if customPortForwarding {
|
||||
Expect(url).To(ContainSubstring(strconv.Itoa(LocalPort)))
|
||||
}
|
||||
resp, err := http.Get(url)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
helper.MatchAllInOutput(string(body), []string{"Hello from Node.js Starter Application!"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
When("modifying memoryLimit for container in Devfile", func() {
|
||||
var stdout string
|
||||
var stderr string
|
||||
When("running odo dev", func() {
|
||||
var devSession helper.DevSession
|
||||
var ports map[string]string
|
||||
BeforeEach(func() {
|
||||
opts := []string{}
|
||||
if customPortForwarding {
|
||||
opts = []string{fmt.Sprintf("--port-forward=%d:%s", localPort1, containerPort1), fmt.Sprintf("--port-forward=%d:%s", localPort2, containerPort2), fmt.Sprintf("--port-forward=%d:%s", localPort3, containerPort3)}
|
||||
}
|
||||
if manual {
|
||||
opts = append(opts, "--no-watch")
|
||||
}
|
||||
var err error
|
||||
devSession, _, _, ports, err = helper.StartDevMode(helper.DevSessionOpts{
|
||||
CmdlineArgs: opts,
|
||||
NoRandomPorts: NoRandomPorts,
|
||||
RunOnPodman: podman,
|
||||
CustomAddress: localAddress,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
devSession.Stop()
|
||||
devSession.WaitEnd()
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("should expose all endpoints on localhost regardless of exposure(podman=%v, manual=%v, customPortForwarding=%v, customAddress=%v)", podman, manual, customPortForwarding, customAddress), func() {
|
||||
By("not exposing debug endpoints", func() {
|
||||
for _, p := range []int{5005, 5006} {
|
||||
_, found := ports[strconv.Itoa(p)]
|
||||
Expect(found).To(BeFalse(), fmt.Sprintf("debug port %d should not be forwarded", p))
|
||||
}
|
||||
})
|
||||
|
||||
getServerResponse := func(containerPort, localPort string) (string, error) {
|
||||
url := fmt.Sprintf("http://%s", ports[containerPort])
|
||||
if customPortForwarding {
|
||||
Expect(url).To(ContainSubstring(localPort))
|
||||
}
|
||||
if customAddress {
|
||||
Expect(url).To(ContainSubstring(localAddress))
|
||||
}
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return string(body), nil
|
||||
}
|
||||
containerPorts := []string{containerPort1, containerPort2, containerPort3}
|
||||
localPorts := []int{localPort1, localPort2, localPort3}
|
||||
|
||||
for i := range containerPorts {
|
||||
containerPort := containerPorts[i]
|
||||
localPort := localPorts[i]
|
||||
By(fmt.Sprintf("exposing a port targeting container port %s", containerPort), func() {
|
||||
r, err := getServerResponse(containerPort, strconv.Itoa(localPort))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
helper.MatchAllInOutput(r, []string{"Hello from Node.js Starter Application!"})
|
||||
})
|
||||
}
|
||||
|
||||
helper.ReplaceString("server.js", "Hello from Node.js", "H3110 from Node.js")
|
||||
|
||||
if manual {
|
||||
if os.Getenv("SKIP_KEY_PRESS") == "true" {
|
||||
Skip("This is a unix-terminal specific scenario, skipping")
|
||||
}
|
||||
}
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
err error
|
||||
stdoutBytes []byte
|
||||
stderrBytes []byte
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
stdoutBytes, stderrBytes, ports, err = devSession.WaitSync()
|
||||
Expect(err).Should(Succeed())
|
||||
stdout = string(stdoutBytes)
|
||||
stderr = string(stderrBytes)
|
||||
}()
|
||||
src := "memoryLimit: 1024Mi"
|
||||
dst := "memoryLimit: 1023Mi"
|
||||
helper.ReplaceString("devfile.yaml", src, dst)
|
||||
if manual {
|
||||
|
||||
devSession.PressKey('p')
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("should react on the Devfile modification (podman=%v, manual=%v, customPortForwarding=%v)", podman, manual, customPortForwarding), func() {
|
||||
if podman {
|
||||
By("warning users that odo dev needs to be restarted", func() {
|
||||
Expect(stdout).To(ContainSubstring(
|
||||
"Detected changes in the Devfile, but this is not supported yet on Podman. Please restart 'odo dev' for such changes to be applied."))
|
||||
})
|
||||
} else {
|
||||
By("not warning users that odo dev needs to be restarted", func() {
|
||||
warning := "Please restart 'odo dev'"
|
||||
Expect(stdout).ShouldNot(ContainSubstring(warning))
|
||||
Expect(stderr).ShouldNot(ContainSubstring(warning))
|
||||
})
|
||||
By("updating the pod", func() {
|
||||
podName := commonVar.CliRunner.GetRunningPodNameByComponent(cmpName, commonVar.Project)
|
||||
bufferOutput := commonVar.CliRunner.Run("get", "pods", podName, "-o", "jsonpath='{.spec.containers[0].resources.requests.memory}'").Out.Contents()
|
||||
output := string(bufferOutput)
|
||||
Expect(output).To(ContainSubstring("1023Mi"))
|
||||
})
|
||||
var stdout, stderr []byte
|
||||
var err error
|
||||
stdout, stderr, _, err = devSession.WaitSync()
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
By("exposing the endpoint", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
url := fmt.Sprintf("http://%s", ports[ContainerPort])
|
||||
if customPortForwarding {
|
||||
Expect(url).To(ContainSubstring(strconv.Itoa(LocalPort)))
|
||||
}
|
||||
resp, err := http.Get(url)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
for _, i := range []string{"Hello from Node.js Starter Application!"} {
|
||||
g.Expect(string(body)).To(ContainSubstring(i))
|
||||
}
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
}).WithPolling(1 * time.Second).WithTimeout(20 * time.Second).Should(Succeed())
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When("devfile has multiple endpoints", func() {
|
||||
var (
|
||||
LocalPort1, LocalPort2, LocalPort3 int
|
||||
)
|
||||
const (
|
||||
// ContainerPort<N> are hard-coded from devfile-with-multiple-endpoints.yaml
|
||||
// Note 1: Debug endpoints will not be exposed for this instance, so we do not add custom mapping for them.
|
||||
// Note 2: We add custom mapping for all the endpoints so that none of them are assigned random ports from the 20001-30001 range;
|
||||
// Note 2(contd.): this is to avoid a race condition where a test running in parallel is also assigned similar ranged port the one here, and we fail to access either of them.
|
||||
ContainerPort1 = "3000"
|
||||
ContainerPort2 = "4567"
|
||||
ContainerPort3 = "7890"
|
||||
)
|
||||
BeforeEach(func() {
|
||||
LocalPort1 = helper.GetCustomStartPort()
|
||||
LocalPort2 = LocalPort1 + 1
|
||||
LocalPort3 = LocalPort1 + 2
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project-with-multiple-endpoints"), commonVar.Context)
|
||||
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-with-multiple-endpoints.yaml")).ShouldPass()
|
||||
})
|
||||
|
||||
When("running odo dev", func() {
|
||||
var devSession helper.DevSession
|
||||
var ports map[string]string
|
||||
BeforeEach(func() {
|
||||
opts := []string{}
|
||||
if customPortForwarding {
|
||||
opts = []string{fmt.Sprintf("--port-forward=%d:%s", LocalPort1, ContainerPort1), fmt.Sprintf("--port-forward=%d:%s", LocalPort2, ContainerPort2), fmt.Sprintf("--port-forward=%d:%s", LocalPort3, ContainerPort3)}
|
||||
}
|
||||
if manual {
|
||||
opts = append(opts, "--no-watch")
|
||||
}
|
||||
var err error
|
||||
devSession, _, _, ports, err = helper.StartDevMode(helper.DevSessionOpts{
|
||||
CmdlineArgs: opts,
|
||||
NoRandomPorts: NoRandomPorts,
|
||||
RunOnPodman: podman,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
devSession.Stop()
|
||||
devSession.WaitEnd()
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("should expose all endpoints on localhost regardless of exposure(podman=%v, manual=%v, customPortForwarding=%v)", podman, manual, customPortForwarding), func() {
|
||||
By("not exposing debug endpoints", func() {
|
||||
for _, p := range []int{5005, 5006} {
|
||||
_, found := ports[strconv.Itoa(p)]
|
||||
Expect(found).To(BeFalse(), fmt.Sprintf("debug port %d should not be forwarded", p))
|
||||
}
|
||||
})
|
||||
|
||||
getServerResponse := func(containerPort, localPort string) (string, error) {
|
||||
url := fmt.Sprintf("http://%s", ports[containerPort])
|
||||
if customPortForwarding {
|
||||
Expect(url).To(ContainSubstring(localPort))
|
||||
}
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return string(body), nil
|
||||
}
|
||||
containerPorts := []string{ContainerPort1, ContainerPort2, ContainerPort3}
|
||||
localPorts := []int{LocalPort1, LocalPort2, LocalPort3}
|
||||
|
||||
for i := range containerPorts {
|
||||
containerPort := containerPorts[i]
|
||||
localPort := localPorts[i]
|
||||
By(fmt.Sprintf("exposing a port targeting container port %s", containerPort), func() {
|
||||
r, err := getServerResponse(containerPort, strconv.Itoa(localPort))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
helper.MatchAllInOutput(r, []string{"Hello from Node.js Starter Application!"})
|
||||
By("not warning users that odo dev needs to be restarted because the Devfile has not changed", func() {
|
||||
warning := "Please restart 'odo dev'"
|
||||
if podman {
|
||||
warning = "Detected changes in the Devfile, but this is not supported yet on Podman. Please restart 'odo dev' for such changes to be applied."
|
||||
}
|
||||
Expect(stdout).ShouldNot(ContainSubstring(warning))
|
||||
Expect(stderr).ShouldNot(ContainSubstring(warning))
|
||||
})
|
||||
}
|
||||
|
||||
helper.ReplaceString("server.js", "Hello from Node.js", "H3110 from Node.js")
|
||||
|
||||
if manual {
|
||||
if os.Getenv("SKIP_KEY_PRESS") == "true" {
|
||||
Skip("This is a unix-terminal specific scenario, skipping")
|
||||
for i := range containerPorts {
|
||||
containerPort := containerPorts[i]
|
||||
localPort := localPorts[i]
|
||||
By(fmt.Sprintf("returning the right response when querying port forwarded for container port %s", containerPort),
|
||||
func() {
|
||||
Eventually(func(g Gomega) string {
|
||||
r, err := getServerResponse(containerPort, strconv.Itoa(localPort))
|
||||
g.Expect(err).ShouldNot(HaveOccurred())
|
||||
return r
|
||||
}, 180, 10).Should(Equal("H3110 from Node.js Starter Application!"))
|
||||
})
|
||||
}
|
||||
|
||||
devSession.PressKey('p')
|
||||
}
|
||||
|
||||
var stdout, stderr []byte
|
||||
var err error
|
||||
stdout, stderr, _, err = devSession.WaitSync()
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
By("not warning users that odo dev needs to be restarted because the Devfile has not changed", func() {
|
||||
warning := "Please restart 'odo dev'"
|
||||
if podman {
|
||||
warning = "Detected changes in the Devfile, but this is not supported yet on Podman. Please restart 'odo dev' for such changes to be applied."
|
||||
}
|
||||
Expect(stdout).ShouldNot(ContainSubstring(warning))
|
||||
Expect(stderr).ShouldNot(ContainSubstring(warning))
|
||||
})
|
||||
|
||||
for i := range containerPorts {
|
||||
containerPort := containerPorts[i]
|
||||
localPort := localPorts[i]
|
||||
By(fmt.Sprintf("returning the right response when querying port forwarded for container port %s", containerPort),
|
||||
func() {
|
||||
Eventually(func(g Gomega) string {
|
||||
r, err := getServerResponse(containerPort, strconv.Itoa(localPort))
|
||||
g.Expect(err).ShouldNot(HaveOccurred())
|
||||
return r
|
||||
}, 180, 10).Should(Equal("H3110 from Node.js Starter Application!"))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user