Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: forward host ports to a container using an SSH tunnel #2471

Merged
merged 29 commits into from
Apr 24, 2024

Conversation

mdelapenya
Copy link
Collaborator

@mdelapenya mdelapenya commented Apr 8, 2024

  • chore: start a foundational package for interacting with Docker networks
  • feat: add an SSH tunnel forwarding a host port to a container

What does this PR do?

This PR defines a new field in the container request struct, so that users can define which host ports are needed to be exposed to a given container.

If set, an internal SSHD server container will be spun before the container, forwarding each exposed host port to the container using an SSH tunnel.

All this logic will happen thanks to the lifecycle container hooks:

  1. if the container declares host ports, we will define a new container lifecycle hook:
  • after the container is ready, we will expose the host ports using a SSH tunnel.
  • before the container is terminates, we will stop the sshd container.
  1. we'll take the first network of the container to attach the sshd container to it
  2. the IP fo the SSHD container in that network will be passed as extra-hosts of the container, so that the host.testcontainers.internal key will land in the container's /etc/hosts file (Linux).

As part of this PR we are adding an internal/core/network package to start adding core, network-related logic in it. This is needed because we need to get the Docker network of the container to pass it to the SSHD container.

Why is it important?

At the moment it's not possible for a container to access a port in the host, which is handy for multiple use cases (please see #2212)

Related issues

Follow-ups

This PR is in draft for one single reason: I'm pushing this branch to receive feedback from the community. I do not want to add any new library for the SSH tunnel, just using the plain stdlib. I see it not very friendly to use it and would like to have more eyes on this feature.

Both Java and .Net use a library to forward the port, and in Go I'd like to avoid that.

At the same time, there is a TODO in the code, as we are adding the network to the sshd container with a functional option that is an exact clone of the network.WithNetwork. If we use it, then a circular dependency will appear, which is not desired. This demonstrates that our efforts for refactoring the API with a more decoupled API is in the right direction.

One final follow-up is to consider starting just one SSHD container per test session, ala Ryuk: at the moment, there is one sshd container per container request. I think we can start this way, make progress, and move to that approach once needed. Thoughts?

@mdelapenya mdelapenya added the feature New functionality or new behaviors on the existing one label Apr 8, 2024
@mdelapenya mdelapenya self-assigned this Apr 8, 2024
Copy link

netlify bot commented Apr 8, 2024

Deploy Preview for testcontainers-go ready!

Name Link
🔨 Latest commit 52d386b
🔍 Latest deploy log https://app.netlify.com/sites/testcontainers-go/deploys/66292801639a9c00083254e8
😎 Deploy Preview https://deploy-preview-2471--testcontainers-go.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

port_forwarding.go Dismissed Show dismissed Hide dismissed
Copy link
Contributor

@codefromthecrypt codefromthecrypt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the start!

some comments about where context can be propagated to facilitate an incoming context with deadline (I use one all the time with tc). Also, some notes about a logic problem I think.

Finally, if anything is replicating logic from another project, maybe worthwhile citing it even if in the same org, as not all maintainers will know to look in the java side for congruence or practice updates.

docker.go Outdated Show resolved Hide resolved
port_forwarding.go Outdated Show resolved Hide resolved
// to allow the container to reach the SSHD container.
hostConfig.ExtraHosts = append(hostConfig.ExtraHosts, fmt.Sprintf("%s:%s", hostInternal, sshdIP))

modes := []container.NetworkMode{container.NetworkMode(sshdFirstNetwork), "none", "host"}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the edge case where sshdFirstNetwork == "" .. is this ok here? If not, I would early exit if len(req.Networks) == 0 a lot higher in this function, as it also reduces indentation and breaks apart some other logic


// do not override the original HostConfigModifier
originalHCM := req.HostConfigModifier
req.HostConfigModifier = func(hostConfig *container.HostConfig) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: not sure about style in this project, but functions like this are easier to unit test if they are not anonymous.

port_forwarding.go Outdated Show resolved Hide resolved
port_forwarding.go Outdated Show resolved Hide resolved
port_forwarding.go Outdated Show resolved Hide resolved
@JulienBreux
Copy link
Contributor

A big thanks from Microcks team 🙏🏻

@mdelapenya
Copy link
Collaborator Author

mdelapenya commented Apr 11, 2024

A big thanks from Microcks team 🙏🏻

🙇 I need to focus on this task, as I found a blocker here: I cannot see the communication being produced between the container and the exposed port in the host. The code is trying to mimic what the Java team is doing:

  • add an extra-hosts to the container with the IP of the sshd server
  • get the first network of the given container and connect the sshd container to it
  • create a ssh client to the sshd server, using the mapped port of the sshd server for the well-known 22.

I verified that it's possible to telnet from the container to the sshd server (port 22) using host.testcontainers.internal with success, but the other extreme of the communication (reaching the port) is not.

Could anybody give this PR a try and help if possible?

@codefromthecrypt
Copy link
Contributor

@joonas-fi I noticed you are doing something similar in your holepunch-client. Though maybe you aren't using testcontainers, yet. If you have some time to spare to help folks get container port forwarding working, much oblidged! https://github.com/function61/holepunch-client/blob/master/cmd/holepunch/client.go

mdelapenya and others added 13 commits April 23, 2024 17:26
* chore: only include the dockerignore if it contains ignore files

* fix: the inclusions must be relative to the context

* docs: document the dockerignore feature

* chore: only include the dockerignore file if it exists
…rs#2475)

* skip search for CACert if ssl has been turned off

* add tests with and without ssl enabled

* add all config keys that disable CA gen, restrict check to version 8

* rename test to match content
Bumps [idna](https://github.com/kjd/idna) from 3.6 to 3.7.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst)
- [Commits](kjd/idna@v3.6...v3.7)

---
updated-dependencies:
- dependency-name: idna
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* fix: remove suspicious filepath.Join

* chore: fix lint

* fix: handle error

* chore: reverse assertion for lint

* feat: support generating TLS certificates on the fly

* chore: apply to cockroachdb

* chore: support saving the cert and priv key files to disk

* chore: apply to rabbitmq

* chore: simplify

* chore: use in redpanda module

* chore: lint

* chore: set validFrom internally

* fix: properly use the new API in redpanda

* docs: document the TLS helpers

* chore: simplify WithParent to accept the struct directly

* chore: use tlscert package instead

* fix: use non-deprecated API

* docs: update

* docs: fix examples

* chore: use released version of tlscert

* fix: add common name for the node cert
* /modules/dolt: wip, kinda working

* /modules/dolt: get tests passing

* /{.github,.vscode,docs,mkdocs,modules,sonar-project}: use modulegen tool

* /modules/dolt/{dolt.go,examples_test.go}: run linter

* /modules/dolt/{dolt.go,examples_test.go}: add methods for cloning

* /{docs, modules}: add with creds file

* /{docs,modules}: pr feedback, cleanup

* /modules/dolt/examples_test.go: remove panics, lint

* chore: run mod tidy

* chore: include MustConnectionString method

* chore: do not use named returns

* chore: perform initialisation before the container has started

---------

Co-authored-by: Manuel de la Peña <mdelapenya@gmail.com>
* Bump default postgres version

* Bump to use latest pg

* Bump version from non-ancient version

---------

Co-authored-by: bstrausser <bstrausser@locusrobotics.com>
* Fix the non-default dbname error

The linked issue described in great detail an issue where we assumed everyone would use the default database user, whose home DB defaults to the postgres database. When that was not the case, the snapshots would fail silently as the user would not connect to the right database to take the commands.

This PR fixes the issue by adding the dbname by default in the command, and adds a test to validate this works as intended. In addition, it also adds some logic to handle any error that does not cause the exec command to fail, such as database access failures.

Run the added test to test this works as intended.

Closes testcontainers#2474

* Document the postgres dbname issue in the docs
…e, compose, qdrant, couchbase, k3s, milvus, mockserver, pulsar, kafka) (testcontainers#2505)

* chore(deps): bump golang.org/x/net in /modules/kafka

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0.
- [Commits](golang/net@v0.17.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump golang.org/x/net in /modules/pulsar

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0.
- [Commits](golang/net@v0.17.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump golang.org/x/net in /modules/mockserver

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0.
- [Commits](golang/net@v0.17.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump golang.org/x/net in /modules/milvus

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0.
- [Commits](golang/net@v0.17.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump golang.org/x/net from 0.19.0 to 0.23.0 in /modules/k3s

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.19.0 to 0.23.0.
- [Commits](golang/net@v0.19.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump golang.org/x/net in /modules/couchbase

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.23.0.
- [Commits](golang/net@v0.20.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump golang.org/x/net in /modules/qdrant

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.23.0.
- [Commits](golang/net@v0.20.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump golang.org/x/net in /modules/compose

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.23.0.
- [Commits](golang/net@v0.20.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump golang.org/x/net in /modules/weaviate

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.23.0.
- [Commits](golang/net@v0.20.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump golang.org/x/net in /modules/gcloud

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.21.0 to 0.23.0.
- [Commits](golang/net@v0.21.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump golang.org/x/net in /modules/minio

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.21.0 to 0.23.0.
- [Commits](golang/net@v0.21.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat: add testcontainers labels to compose containers

* feat: support reaper for compose

* chore: increase ryuk reconnection timeout on CI

* chore: cache containers on UP

* chore: more tuning for compose

* chore: more consistent assertion

* chore: the compose stack asks for the reaper, but each container then connects to it

* chore: use different error groups

the first time wait is called, the context is cancelled

* chore: the lookup method include cache checks

* chore: update tests to make them deterministic

* chore: rename local compose testss

* chore: support returning the dynamic port in the helper function

* chore: try with default reconnection timeout

* feat: support removing networks from compose

* chore: support naming test services with local and api

It will allow the tests to be more deterministic, as there could be service containers started from the local test suite with the same name as in the API test suite.

* Revert "chore: try with default reconnection timeout"

This reverts commit 336760c.

* fix: typo
…pose instance (testcontainers#2509)

* feat: support passing io.Reader when creating a compose instance

* docs: change title
…stcontainers#2511)

* feat: support overriding the default recreate options for compose

* chore: validate recreation values
p-jahn and others added 4 commits April 23, 2024 17:26
* fix: don't retry on permanent APIClient errors

* fix: add more tests for un-retryable scenarios
* main:
  fix: don't retry on permanent APIClient errors (testcontainers#2506)
  feat: support overriding the default recreate options for compose (testcontainers#2511)
  feat: support passing io.Reader for compose files when creating a compose instance (testcontainers#2509)
  chore: add funding button for testcontainers (testcontainers#2510)
  feat: support Ryuk for the compose module (testcontainers#2485)
  chore(deps): bump golang.org/x/net in modules (minio, gcloud, weaviate, compose, qdrant, couchbase, k3s, milvus, mockserver, pulsar, kafka) (testcontainers#2505)
  fix: fallback to URL-path when parsing auth config URL without scheme (testcontainers#2488)
  fix(postgres): Fix the non-default dbname error (testcontainers#2489)
  feat: Bump default postgres version (testcontainers#2481)
  support Dolt (testcontainers#2177)
  chore: create TLS certs in a consistent manner (testcontainers#2478)
  chore(deps): bump idna from 3.6 to 3.7 (testcontainers#2480)
  Elasticsearch disable CA retrieval when ssl is disabled (testcontainers#2475)
  fix: handle dockerignore exclusions properly (testcontainers#2476)
@mdelapenya
Copy link
Collaborator Author

@codefromthecrypt @JulienBreux could you give this one a try? 🙏 I think it's ready for review

@JulienBreux
Copy link
Contributor

@codefromthecrypt @JulienBreux could you give this one a try? 🙏 I think it's ready for review

Let's go to testing at 2 PM today.

@JulienBreux
Copy link
Contributor

JulienBreux commented Apr 24, 2024

Hey @mdelapenya 👋🏻

Actually, I use this file to test, and it works well. 🎉 🎉 🎉

Design explanation :

  1. Create a local HTTP server http://127.0.0.1:7171.
  2. Create a TC "alpine" with HostAccessPorts: []int{7171}.
  3. Exec a CMD curl -s -L http://host.testcontainers.internal:7171/ from the TC "alpine".

https://gist.github.com/JulienBreux/3892189373cb246c180a0d8d2d1928c7

@mdelapenya mdelapenya marked this pull request as ready for review April 24, 2024 15:14
@mdelapenya mdelapenya requested a review from a team as a code owner April 24, 2024 15:14
port_forwarding.go Outdated Show resolved Hide resolved
@mdelapenya mdelapenya merged commit 15d5bbc into testcontainers:main Apr 24, 2024
104 checks passed
mdelapenya added a commit to stevenh/testcontainers-go that referenced this pull request Apr 24, 2024
* main: (22 commits)
  feat: forward host ports to a container using an SSH tunnel (testcontainers#2471)
  Update follow_logs.md with adding missing package (testcontainers#2513)
  fix: don't retry on permanent APIClient errors (testcontainers#2506)
  feat: support overriding the default recreate options for compose (testcontainers#2511)
  feat: support passing io.Reader for compose files when creating a compose instance (testcontainers#2509)
  chore: add funding button for testcontainers (testcontainers#2510)
  feat: support Ryuk for the compose module (testcontainers#2485)
  chore(deps): bump golang.org/x/net in modules (minio, gcloud, weaviate, compose, qdrant, couchbase, k3s, milvus, mockserver, pulsar, kafka) (testcontainers#2505)
  fix: fallback to URL-path when parsing auth config URL without scheme (testcontainers#2488)
  fix(postgres): Fix the non-default dbname error (testcontainers#2489)
  feat: Bump default postgres version (testcontainers#2481)
  support Dolt (testcontainers#2177)
  chore: create TLS certs in a consistent manner (testcontainers#2478)
  chore(deps): bump idna from 3.6 to 3.7 (testcontainers#2480)
  Elasticsearch disable CA retrieval when ssl is disabled (testcontainers#2475)
  fix: handle dockerignore exclusions properly (testcontainers#2476)
  chore: prepare for next minor development cycle (0.31.0)
  chore: use new version (v0.30.0) in modules and examples
  Fix url creation to handle query params when using HTTP wait strategy (testcontainers#2466)
  fix: data race on container run (testcontainers#2345)
  ...
@mdelapenya mdelapenya deleted the sshd-port-forwarding branch April 25, 2024 07:30
mdelapenya added a commit to mdelapenya/testcontainers-go that referenced this pull request Apr 26, 2024
* main: (34 commits)
  break: return error from Customize request option (testcontainers#2267)
  fix: wrong copy paste (testcontainers#2515)
  docs: add documentation for Exec method (testcontainers#2451)
  docs: document the SSHd tunnel (testcontainers#2514)
  fix: enhance host configuration port binding (testcontainers#2512)
  feat: forward host ports to a container using an SSH tunnel (testcontainers#2471)
  Update follow_logs.md with adding missing package (testcontainers#2513)
  fix: don't retry on permanent APIClient errors (testcontainers#2506)
  feat: support overriding the default recreate options for compose (testcontainers#2511)
  feat: support passing io.Reader for compose files when creating a compose instance (testcontainers#2509)
  chore: add funding button for testcontainers (testcontainers#2510)
  feat: support Ryuk for the compose module (testcontainers#2485)
  chore(deps): bump golang.org/x/net in modules (minio, gcloud, weaviate, compose, qdrant, couchbase, k3s, milvus, mockserver, pulsar, kafka) (testcontainers#2505)
  fix: fallback to URL-path when parsing auth config URL without scheme (testcontainers#2488)
  fix(postgres): Fix the non-default dbname error (testcontainers#2489)
  feat: Bump default postgres version (testcontainers#2481)
  support Dolt (testcontainers#2177)
  chore: create TLS certs in a consistent manner (testcontainers#2478)
  chore(deps): bump idna from 3.6 to 3.7 (testcontainers#2480)
  Elasticsearch disable CA retrieval when ssl is disabled (testcontainers#2475)
  ...
mdelapenya added a commit to khartld/testcontainers-go that referenced this pull request May 7, 2024
* main: (44 commits)
  feat: expose JSON representation of a container with Inspect (testcontainers#2534)
  chore(deps): bump test-summary action to v2.3 (testcontainers#2535)
  chore(deps): bump jinja2 from 3.1.3 to 3.1.4 (testcontainers#2533)
  Update devcontainer image (testcontainers#2531)
  chore(influxdb): include more characters in wait for log regex (testcontainers#2532)
  fix(compose): avoid race conditions when caching services (testcontainers#2528)
  chore(deps): bump golangci/golangci-lint-action from 3.7.0 to 5.1.0 (testcontainers#2525)
  chore(deps): bump mkdocs-material from 8.2.7 to 9.1.21 (testcontainers#2524)
  chore(compose): return error in options (testcontainers#2520)
  chore(deps): bump github.com/compose-spec/compose-go/v2 from v2.0.0-rc8 to v2.1.0 (testcontainers#2519)
  chore(deps): bump github.com/containerd/containerd from 1.7.12 to 1.7.15 (testcontainers#2517)
  break: return error from Customize request option (testcontainers#2267)
  fix: wrong copy paste (testcontainers#2515)
  docs: add documentation for Exec method (testcontainers#2451)
  docs: document the SSHd tunnel (testcontainers#2514)
  fix: enhance host configuration port binding (testcontainers#2512)
  feat: forward host ports to a container using an SSH tunnel (testcontainers#2471)
  Update follow_logs.md with adding missing package (testcontainers#2513)
  fix: don't retry on permanent APIClient errors (testcontainers#2506)
  feat: support overriding the default recreate options for compose (testcontainers#2511)
  ...
@codefromthecrypt
Copy link
Contributor

probably just me, but I struggled on this in k3s (wanting a pod to call a service on my host). 1000x thanks if someone has a gist or contributes an integration test to the k3s module for this.

@JulienBreux
Copy link
Contributor

Hi @codefromthecrypt,

Do you've a reproducible architecture? I need to test more.
Thanks!

@codefromthecrypt
Copy link
Contributor

codefromthecrypt commented May 31, 2024

@JulienBreux (cc @salaboy as he probably is interested in this)

So, I was testing coredns dnstap plugin (wanting to have traffic flow out to a listener I setup in the go test). However, since the PR here is about http and should work similar, I've a rough test below (pasted into k3s_test.go). I'm not sure I need an external name, but with or without it, I get "bad address" from alpine.

const (
	expectedResponse = "Hello, World!"
)

func Test_WithHostPortAccess(t *testing.T) {
	ctx := context.Background()

	port, err := getFreePort()
	if err != nil {
		t.Fatal(err)
	}
	portString := fmt.Sprintf("%d", port)

	// Create a local HTTP server
	server := http.Server{Addr: ":" + portString, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, expectedResponse)
	})}
	go server.ListenAndServe()
	defer server.Close()

	k3sContainer, err := k3s.RunContainer(ctx,
		testcontainers.WithImage("docker.io/rancher/k3s:v1.27.1-k3s1"),
		testcontainers.WithHostPortAccess(port),
	)
	if err != nil {
		t.Fatal(err)
	}

	// Clean up the container
	defer func() {
		if err := k3sContainer.Terminate(ctx); err != nil {
			t.Fatal(err)
		}
	}()

	kubeConfigYaml, err := k3sContainer.GetKubeConfig(ctx)
	if err != nil {
		t.Fatal(err)
	}

	restcfg, err := clientcmd.RESTConfigFromKubeConfig(kubeConfigYaml)
	if err != nil {
		t.Fatal(err)
	}

	k8s, err := kubernetes.NewForConfig(restcfg)
	if err != nil {
		t.Fatal(err)
	}

	// Create a CNAME httptest pointing to host.testcontainers.internal
	service := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name: "httptest",
		},
		Spec: corev1.ServiceSpec{
			Type:         corev1.ServiceTypeExternalName,
			ExternalName: "host.testcontainers.internal",
		},
	}
	services := k8s.CoreV1().Services("default")
	if _, err = services.Create(ctx, service, metav1.CreateOptions{}); err != nil {
		t.Fatal(err)
	}

	// Block until the service is ready to avoid race conditions
	if err = kwait.PollUntilContextCancel(ctx, time.Second, true, func(ctx context.Context) (bool, error) {
		if s, err := services.Get(ctx, service.Name, metav1.GetOptions{}); err != nil {
			return false, err
		} else if s.Spec.Type != service.Spec.Type || s.Spec.ExternalName != service.Spec.ExternalName {
			return false, fmt.Errorf("unexpected service spec: %v", s.Spec)
		}
		return true, nil
	}); err != nil {
		t.Fatal(err)
	}

	// Create a pod that attempts to access the host's http service using wget
	pod := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test-pod",
		},
		Spec: corev1.PodSpec{
			Containers: []corev1.Container{
				{
					Name:    "alpine",
					Image:   "alpine:3.17",
					Command: []string{"wget", "-qO-", fmt.Sprintf("http://%s:%d", service.Name, port)},
				},
			},
			RestartPolicy: corev1.RestartPolicyNever,
		},
	}

	pods := k8s.CoreV1().Pods("default")
	if _, err = pods.Create(ctx, pod, metav1.CreateOptions{}); err != nil {
		t.Fatal(err)
	}

	// Wait for the wget to complete
	if err = kwait.PollUntilContextCancel(ctx, time.Second, true, func(ctx context.Context) (bool, error) {
		pod, err := pods.Get(ctx, "test-pod", metav1.GetOptions{})
		if err != nil {
			return false, err
		}

		for _, status := range pod.Status.ContainerStatuses {
			if status.State.Terminated != nil {
				return true, nil
			}
		}

		return false, nil
	}); err != nil {
		t.Fatal(err)
	}

	// Check the logs of the Pod to ensure that we read the expected response
	logs, err := pods.GetLogs("test-pod", &corev1.PodLogOptions{}).DoRaw(ctx)
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(string(logs), expectedResponse) {
		t.Fatalf("wget did not return the expected response, got: %s", logs)
	}
}

// getFreePort asks the kernel for a free open port that is ready to use.
func getFreePort() (int, error) {
	addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
	if err != nil {
		return 0, err
	}

	l, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return 0, err
	}
	defer l.Close()
	return l.Addr().(*net.TCPAddr).Port, nil
}

FYI I am running docker via colima start --cpu 4 --memory 8 --network-address, not docker desktop or otherwise. Also, I'm moving to singapore this weekend, so won't be able to revise this, but anyone has license to this if they want to make a PR out of it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New functionality or new behaviors on the existing one
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature] Add withAccessToHost option to access to host from containers
8 participants