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

Allow user defined dev services for any image #42587

Closed
wants to merge 1 commit into from

Conversation

phillip-kruger
Copy link
Member

This PR propose an enhanced Dev Services to also allow user defined services for any container image. This is useful when users have multiple microservices of their own that can now be started up by a Quarkus app that depends on it.

Here is an example where I have an app with the following config to start up 3 dev services with my Quarkus app (nginx, httpd and hello-world):

# Any container
quarkus.devservices."nginx".image-name=nginx
quarkus.devservices."nginx".container-port=80
quarkus.devservices."nginx".mapped-port=7070
quarkus.devservices."nginx".capture-log=true
quarkus.devservices."nginx".classpath-resource-mappings."nginx.conf".container-path=/etc/nginx/nginx.conf
quarkus.devservices."nginx".file-system-bindings."/tmp/nginx".container-path=/usr/share/nginx/html
quarkus.devservices."nginx".label."by".value=Phillip Kruger
quarkus.devservices."nginx".wait-for.http=/

quarkus.devservices."httpd".image-name=httpd
quarkus.devservices."httpd".container-port=80

quarkus.devservices."hello-world".image-name=hello-world

The hello world and httpd is just there to test the minimal config needed and to test multiple user defined dev services.

If you look at the nginx config you can see this is a bit more configured, and actually serve static pages from /tmp/http. It also captures the log:

2024-08-16 20:58:25,022 INFO  [org.tes.DockerClientFactory] (build-31) Checking the system...
2024-08-16 20:58:25,022 INFO  [org.tes.DockerClientFactory] (build-31) ✔︎ Docker server version should be at least 1.6.0
2024-08-16 20:58:25,150 INFO  [tc.nginx:latest] (build-31) Creating container for image: nginx:latest
2024-08-16 20:58:25,516 WARN  [org.tes.con.ContainerDef] (build-31) Unable to mount a file from test host into a running container. This may be a misconfiguration or limitation of your Docker environment. Some features might not work.
2024-08-16 20:58:25,619 INFO  [tc.nginx:latest] (build-31) Container nginx:latest is starting: 4ea2432e1c040da40f72b64406d54fe7f3e45858552ad2e5b20c6dda0ea22e95
2024-08-16 20:58:25,664 INFO  [io.qua.dat.dep.dev.DevServicesDatasourceProcessor] (build-33) Dev Services for default datasource (postgresql) started - container ID is b5e085c43f7f
2024-08-16 20:58:25,778 INFO  [io.qua.dev.dep.any.DevServicesProcessor] (docker-java-stream--1341124604) [nginx] STDOUT: /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
2024-08-16 20:58:25,778 INFO  [io.qua.dev.dep.any.DevServicesProcessor] (docker-java-stream--1341124604) [nginx] STDOUT: /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
2024-08-16 20:58:25,779 INFO  [io.qua.dev.dep.any.DevServicesProcessor] (docker-java-stream--1341124604) [nginx] STDOUT: /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
2024-08-16 20:58:25,780 INFO  [io.qua.dev.dep.any.DevServicesProcessor] (docker-java-stream--1341124604) [nginx] STDOUT: 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
2024-08-16 20:58:25,780 INFO  [io.qua.dev.dep.any.DevServicesProcessor] (docker-java-stream--1341124604) [nginx] STDOUT: 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
2024-08-16 20:58:25,781 INFO  [io.qua.dev.dep.any.DevServicesProcessor] (docker-java-stream--1341124604) [nginx] STDOUT: /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
2024-08-16 20:58:25,782 INFO  [io.qua.dev.dep.any.DevServicesProcessor] (docker-java-stream--1341124604) [nginx] STDOUT: /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
2024-08-16 20:58:25,782 INFO  [io.qua.dev.dep.any.DevServicesProcessor] (docker-java-stream--1341124604) [nginx] STDOUT: /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
2024-08-16 20:58:25,783 INFO  [io.qua.dev.dep.any.DevServicesProcessor] (docker-java-stream--1341124604) [nginx] STDOUT: /docker-entrypoint.sh: Configuration complete; ready for start up
2024-08-16 20:58:25,876 INFO  [org.tes.con.wai.str.HttpWaitStrategy] (build-31) /nifty_austin: Waiting for 60 seconds for URL: http://localhost:7070/ (where port 7070 maps to container port 80)
2024-08-16 20:58:25,882 INFO  [io.qua.dev.dep.any.DevServicesProcessor] (docker-java-stream--1341124604) [nginx] STDOUT: 10.89.24.2 - - [16/Aug/2024:10:58:25 +0000] "GET / HTTP/1.1" 200 305 "-" "Java/21.0.4"
2024-08-16 20:58:25,884 INFO  [tc.nginx:latest] (build-31) Container nginx:latest started in PT0.733859172S
2024-08-16 20:58:25,891 INFO  [tc.hello-world:latest] (build-31) Creating container for image: hello-world:latest
2024-08-16 20:58:25,919 INFO  [tc.hello-world:latest] (build-31) Container hello-world:latest is starting: 4f6c3b9e02b99482e1dce288ca061e00359cf967647e41d01a0bf2920c6a043a
2024-08-16 20:58:26,120 INFO  [tc.hello-world:latest] (build-31) Container hello-world:latest started in PT0.22915224S
2024-08-16 20:58:26,125 INFO  [tc.httpd:latest] (build-31) Creating container for image: httpd:latest
2024-08-16 20:58:26,152 INFO  [tc.httpd:latest] (build-31) Container httpd:latest is starting: 75ae69387255a606635d03430514a39923755f59607a3f7063ec281ef52c10ac
2024-08-16 20:58:26,445 INFO  [tc.httpd:latest] (build-31) Container httpd:latest started in PT0.319468388S
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2024-08-16 20:58:27,433 INFO  [io.und.websockets] (Quarkus Main Thread) UT026003: Adding annotated server endpoint class io.quarkus.sample.audit.AuditLogSocket for path /audit

2024-08-16 20:58:28,516 INFO  [io.quarkus] (Quarkus Main Thread) todo-backend 1.0-SNAPSHOT on JVM (powered by Quarkus 999-SNAPSHOT) started in 4.847s. Listening on: http://localhost:8080
2024-08-16 20:58:28,518 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2024-08-16 20:58:28,518 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, hibernate-validator, jdbc-postgresql, narayana-jta, rest, rest-jsonb, smallrye-context-propagation, smallrye-graphql, smallrye-health, smallrye-metrics, smallrye-openapi, swagger-ui, vertx, web-dependency-locator, websockets, websockets-client]

nginx

The user defined dev services will also show up in Dev UI:

any_dev_services

For a user to use this, just add :

<dependency>
          <groupId>io.quarkus</groupId>
          <artifactId>quarkus-devservices</artifactId>
</dependency>

@quarkus-bot quarkus-bot bot added area/dependencies Pull requests that update a dependency file area/devservices labels Aug 16, 2024
@geoand
Copy link
Contributor

geoand commented Aug 16, 2024

Cool stuff!

I'll have a close look at the PR when I'm back (in 10 days or so)

@quarkus-bot

This comment has been minimized.

Copy link
Contributor

@gastaldi gastaldi left a comment

Choose a reason for hiding this comment

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

Some minor nitpicks, looks awesome! Great job

@gastaldi
Copy link
Contributor

We need documentation/guide for this new extension too

@holly-cummins
Copy link
Contributor

This will be really useful, IMO. The code looks similar to the patterns I was able to work out for #42535, so to my knowledge, it's correct. :) We should include a reference to this in the dev services doc as a nice clean example, once it merges.

I have a few little questions:

Reuse + mapped ports

I wonder if false is the right default for reuse, in the presence of mapped ports? I found live reload pretty intolerable with fixed ports and reuse false. But have a default change depending on other values is complex, so maybe we just document that people should turn reuse off if they set the port? But that leads me to ...

Documentation

There doesn't seem to be any. :)

Reuse, again

I noticed that a lot of the extensions which reused containers, like ElasticSearch, did really complex things about storing static variables and customising shutdown. I think part of that comes when the container is shared across several instances, so maybe we can ignore all that, but I did feel uneasy that my example service was so much simpler than the ones in the Quarkus codebase. The same worry applies here, and @gsmet did imply some of the complexity was necessary - but maybe it's just a question of articulating when the complexity is needed and when it's not.

Extension name

My main question is about the name. I wonder if users might think they need to add this extension, to get dev services? And I wonder if extension-developers might get confused about what the difference is between this and dev-services-common? I don't have any very good suggestions, though - dev-services-config or config-dev-services makes it sound like something for configuring dev services, configurable-dev-services is clearer but not that clear and it's awfully long, and has the words in the wrong order. dev-services-easy or dev-services-quick or something might make the intention clear?

@quarkus-bot

This comment has been minimized.

Copy link

github-actions bot commented Aug 16, 2024

🎊 PR Preview 6b0a25d has been successfully built and deployed to https://quarkus-pr-main-42587-preview.surge.sh/version/main/guides/

  • Images of blog posts older than 3 months are not available.
  • Newsletters older than 3 months are not available.

@melloware
Copy link
Contributor

agree with @holly-cummins there is a lot of code that checks for "shared" dev services etc and proper shutdown. I follow those patterns because a lot of the built in Quarkus Dev Services follow that pattern.

@phillip-kruger
Copy link
Member Author

Hi @holly-cummins , @gastaldi , @melloware . Thanks for your feedback.

@gastaldi - I applied your suggestions.
@holly-cummins:

Reuse + mapped ports
So will this be a better default:

// Reuse
// If not provided, and the mapped port is configured, we want reuse true as a default, else false
if(config.reuse().isPresent()){
    container = container.withReuse(config.reuse().get());
}else if(config.mappedPort().isPresent()){
    container = container.withReuse(true);
}else{
    container = container.withReuse(false);
}

Documentation
Yes, I was thinking to do this in another PR.

Reuse, again
So I am sure there will be cases that this "User defined dev services" will not work, not just from a reuse p.o.v. but from any other aspect. For those cases, you will need to write an extension. This target simple cases, and private / homegrown cases, example, I have 3 microservices, all written in Java and all containerized and available to be pulled with podman/docker. One service needs the other two to run, so I can configure them here and my app will just work in dev mode. (something like that?)

Extension name
I actually initially had this extension as a separate extension, but then I saw there is already a quarkus-devservices extension, it just did not have a runtime module. I am happy either way, we can move it to it's own extension (something like quarkus-user-devservices or quarkus-app-devservices. Just let me know. It could even be in quarkiverse

@quarkus-bot
Copy link

quarkus-bot bot commented Aug 19, 2024

Status for workflow Quarkus CI

This is the status report for running Quarkus CI on commit b60ab6d.

Failing Jobs

Status Name Step Failures Logs Raw logs Build scan
Initial JDK 17 Build Build Failures Logs Raw logs 🔍

You can consult the Develocity build scans.

Failures

⚙️ Initial JDK 17 Build #

- Failing: extensions/agroal/runtime extensions/elasticsearch-rest-client-common/runtime extensions/infinispan-client/runtime and 16 more
! Skipped: devtools/bom-descriptor-json docs extensions/agroal/deployment and 324 more

📦 extensions/agroal/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-agroal: The deployment artifact io.quarkus:quarkus-agroal-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-agroal:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/elasticsearch-rest-client-common/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-elasticsearch-rest-client-common: The deployment artifact io.quarkus:quarkus-elasticsearch-rest-client-common-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-elasticsearch-rest-client-common:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/infinispan-client/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-infinispan-client: The deployment artifact io.quarkus:quarkus-infinispan-client-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-infinispan-client:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/kafka-client/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-kafka-client: The deployment artifact io.quarkus:quarkus-kafka-client-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-kafka-client:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/kubernetes-client/runtime-internal

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-kubernetes-client-internal: The deployment artifact io.quarkus:quarkus-kubernetes-client-internal-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-kubernetes-client-internal:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/mongodb-client/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-mongodb-client: The deployment artifact io.quarkus:quarkus-mongodb-client-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-mongodb-client:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/observability-devservices/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-observability-devservices: The deployment artifact io.quarkus:quarkus-observability-devservices-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-observability-devservices:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/oidc-client/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-oidc-client: The deployment artifact io.quarkus:quarkus-oidc-client-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-oidc-client:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/oidc/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-oidc: The deployment artifact io.quarkus:quarkus-oidc-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-oidc:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/reactive-datasource/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-reactive-datasource: The deployment artifact io.quarkus:quarkus-reactive-datasource-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-reactive-datasource:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/redis-client/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-redis-client: The deployment artifact io.quarkus:quarkus-redis-client-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-redis-client:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/schema-registry/apicurio/avro/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-apicurio-registry-avro: The deployment artifact io.quarkus:quarkus-apicurio-registry-avro-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-apicurio-registry-avro:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/schema-registry/apicurio/json-schema/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-apicurio-registry-json-schema: The deployment artifact io.quarkus:quarkus-apicurio-registry-json-schema-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-apicurio-registry-json-schema:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/schema-registry/confluent/avro/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-confluent-registry-avro: The deployment artifact io.quarkus:quarkus-confluent-registry-avro-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-confluent-registry-avro:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/schema-registry/confluent/json-schema/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-confluent-registry-json-schema: The deployment artifact io.quarkus:quarkus-confluent-registry-json-schema-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-confluent-registry-json-schema:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/smallrye-reactive-messaging-amqp/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-messaging-amqp: The deployment artifact io.quarkus:quarkus-messaging-amqp-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-messaging-amqp:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/smallrye-reactive-messaging-mqtt/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-messaging-mqtt: The deployment artifact io.quarkus:quarkus-messaging-mqtt-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-messaging-mqtt:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/smallrye-reactive-messaging-pulsar/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-messaging-pulsar: The deployment artifact io.quarkus:quarkus-messaging-pulsar-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-messaging-pulsar:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

📦 extensions/smallrye-reactive-messaging-rabbitmq/runtime

Failed to execute goal io.quarkus:quarkus-extension-maven-plugin:999-SNAPSHOT:extension-descriptor (generate-extension-descriptor) on project quarkus-messaging-rabbitmq: The deployment artifact io.quarkus:quarkus-messaging-rabbitmq-deployment::jar depends on the following Quarkus extension deployment artifacts whose corresponding runtime artifacts were not found among the dependencies of io.quarkus:quarkus-messaging-rabbitmq:jar:999-SNAPSHOT: io.quarkus:quarkus-devservices-deployment::jar

@quarkus-bot
Copy link

quarkus-bot bot commented Aug 19, 2024

Status for workflow Quarkus Documentation CI

This is the status report for running Quarkus Documentation CI on commit b60ab6d.

✅ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

@melloware
Copy link
Contributor

@phillip-kruger another optiion you might want to offer is

quarkus.devservices."nginx".logging.enabled=true

in Mailpit I have this JbossLogConsumer https://github.com/quarkiverse/quarkus-mailpit/blob/main/deployment/src/main/java/io/quarkiverse/mailpit/deployment/JbossContainerLogConsumer.java

Which hooks into TestContainers to print out the logs of the container you just wire it in like this.

// forward the container logs
super.withLogConsumer(new JbossContainerLogConsumer(log).withPrefix(MailpitProcessor.FEATURE));

@phillip-kruger
Copy link
Member Author

That is already there (I copied the one from Mailpit, thanks:) ) quarkus.devservices."nginx".capture-log=true

@melloware
Copy link
Contributor

Nice! i don't know how i missed that!

@phillip-kruger
Copy link
Member Author

Closing here. Discussed with @cescoffier - we are going to take another approach.

@quarkus-bot quarkus-bot bot added the triage/invalid This doesn't seem right label Aug 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/dependencies Pull requests that update a dependency file area/devservices release/noteworthy-feature triage/invalid This doesn't seem right
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants