diff --git a/docs/_workshop.yml b/docs/_workshop.yml index 59944d0..560d31b 100644 --- a/docs/_workshop.yml +++ b/docs/_workshop.yml @@ -1,6 +1,6 @@ --- id: quarkus-lab -name: Quarkus Hands-on Lab +name: Quarkus Hands-on Lab (Lab Version 2019-11-07) vars: ROUTE_SUBDOMAIN: @@ -30,4 +30,4 @@ modules: - kafka - monitoring - tracing - - security \ No newline at end of file + - security diff --git a/docs/cloudnative.adoc b/docs/cloudnative.adoc index 2600e72..39e7cad 100644 --- a/docs/cloudnative.adoc +++ b/docs/cloudnative.adoc @@ -90,7 +90,7 @@ There are two types of probes in Quarkus apps (and Kubernetes): Readiness and liveness probes can be used in parallel for the same container. Using both can ensure that traffic does not reach a container that is not ready for it, and that containers are restarted when they fail. There are various https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/[Configuration Paramters, window=_blank] you can set, such as the timeout period, frequency, and other parameters that can be tuned to expected application behavior. ==== -THanks to Live Coding mode, simply open a Terminal window and run: +Thanks to Live Coding mode, simply open a Terminal window and run: [source, sh, role="copypaste"] ---- @@ -443,6 +443,8 @@ Next, let's re-build the app as an executable JAR (which will run with the `prod Build an executable JAR just as before using the command palette and choosing **Create Executable JAR**. +image:createexec.png[create,600] + Next, open a new Terminal window and run the the app: [source,sh,role="copypaste"] diff --git a/docs/debugging.adoc b/docs/debugging.adoc index fd3ab1e..683ecfe 100644 --- a/docs/debugging.adoc +++ b/docs/debugging.adoc @@ -17,6 +17,7 @@ Open up the `GreetingResource.java` class again, and add another RESTful endpoin public String lastLetter(@PathParam("name") String name) { int len = name.length(); String lastLetter = name.substring(len); + log.info("Got last letter: " + lastLetter); return lastLetter; } ---- @@ -28,7 +29,7 @@ A a bug has been reported where the last letter is not working. To reproduce the curl http://localhost:8080/hello/lastletter/Foo ---- -Due to the bug, nothing is returned. It should have given us back an `o`. +Due to the bug, nothing is returned. It should have given us back an `o`. You'll also see `Got last letter:` and then no letter. You can probably spot the bug right away but let's see how you could find this with the debugger, and more importantly fix it and re-test very quickly. @@ -50,6 +51,8 @@ image::buttons.png[buttons, 600] To debug the app, let's step through our function that has the bug. In the left gutter of the code, where the line numbers are shown, click once on the line number next to `int len = name.length();` to set a breakpoint. The line number will be highlighted and the breakpoint will be registered in the debug pane: +> **NOTE** - your line numbers may be different depending on where you placed this code in the file. + image::break.png[breakpoint,800] == Trigger the bug @@ -87,7 +90,7 @@ Click **Step Over** again, which executes the line to grab the last letter using We need to pass an offset that is one _before_ the end, to get the last letter. -Click the **Resume** button to let the method continue and return the value (your `curl` command has probably timed out by now). +Click the **Resume** button to let the method continue, log the erroneous value to the console, and return the value (your `curl` command has probably timed out by now). == Fix the bug @@ -105,7 +108,7 @@ With the bug fixed, re-trigger the method by running the `curl` command again in curl http://localhost:8080/hello/lastletter/foo ---- -The breakpoint will be hit once again. Step over the lines to verify the value of `lastLetter` is correct before the method returns. You've fixed the bug! +The breakpoint will be hit once again. Step over the lines to verify the value of `lastLetter` is correct and you see a proper `Got last letter: o` on the console. before the method returns. You've fixed the bug! [WARNING] ==== diff --git a/docs/deploy.adoc b/docs/deploy.adoc index 195c738..86ccff8 100644 --- a/docs/deploy.adoc +++ b/docs/deploy.adoc @@ -80,18 +80,11 @@ oc start-build people --from-file target/*-runner --follow This step will combine the native binary with a base OS image, create a new container image, and push it to an internal image registry. -Once that's done, deploy the new image as an OpenShift application: +Once that's done, deploy the new image as an OpenShift application and expose its HTTP endpoint to the outside world: [source,sh,role="copypaste"] ---- -oc new-app people ----- - -and expose it to the world: - -[source,sh,role="copypaste"] ----- -oc expose svc/people +oc new-app people && oc expose svc/people ---- Finally, make sure it's actually done rolling out: diff --git a/docs/extensions.adoc b/docs/extensions.adoc index 253a95e..103c697 100644 --- a/docs/extensions.adoc +++ b/docs/extensions.adoc @@ -30,40 +30,6 @@ Apache Tika quarkus-tika Adding an extension is simiarly easy. With Maven, you can add extensions using `mvn quarkus:add-extension -Dextensions="extension1,extension2,..."`. The extension name can be the maven groupId/artifactId name of the extension: e.g. `io.quarkus:quarkus-agroal`. But you can pass a partial name and Quarkus will do its best to find the right extension. For example, `agroal`, `Agroal` or `agro` will expand to `io.quarkus:quarkus-agroal`. If no extension is found or if more than one extensions match, you will see a warning and a list of possible matches in the command result. -== Add an extension - -Later on in this lab we'll be using MicroProfile metrics, so let's add that extension here. In the Terminal, run the following command to add the _MicroProfile Metrics_ extension to your project: - -[source,sh,role="copypaste"] ----- -mvn quarkus:add-extension -Dextensions="metrics" ----- - -Notice we are using the "short" name `metrics` instead of the fully qualified name `io.quarkus:quarkus-smallrye-metrics`. - -The result of this command is a new `` added to our `pom.xml` which you can see by looking at the differences using the following command: - -[source,sh,role="copypaste"] ----- -git --no-pager diff pom.xml ----- - -You will see all the changes to `pom.xml` since you started: - -[source, none] ----- -+ -+ io.quarkus -+ quarkus-smallrye-health -+ -+ -+ io.quarkus -+ quarkus-smallrye-metrics -+ ----- - -The `quarkus-smallrye-health` was added in a previous exercise, and shows up here as well. - When you run Quarkus applications, the list of extensions enabled are shown in the output, such as: [source, none] @@ -76,6 +42,34 @@ INFO [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc- In Live Coding mode, Quarkus will monitor the state of `pom.xml` and bring in new dependencies. No need to stop and restart! ==== + +== Add an extension + +Later on in this lab we'll be using MicroProfile metrics, so let's add that extension here. In the Terminal, run the following command to add the _MicroProfile Metrics_ extension to your project: + +[source,sh,role="copypaste"] +---- +mvn quarkus:add-extension -Dextensions="metrics" +---- + +Notice we are using the "short" name `metrics` instead of the fully qualified name `io.quarkus:quarkus-smallrye-metrics`. + +The result of this command is a new `` added to our `pom.xml` which you can see by looking at the differences you've made up till now. + +Right-click on your `pom.xml` and select _Git > Compare with latest repository version_: + +image::gitdiff.png[login,600] + +You'll see all the changes to `pom.xml` since you started: + +image::gitdiffcode.png[login,700] + +You may see other apparent differences due to whitespace and/or the re-shuffling of XML elements when you ran `mvn quarkus:add-extension`. + +There are many other git and GitHub operations like this one that you can perform directly in the IDE for real projects (e.g. committing, branching, merging, push/pull, log viewing, etc). + +Close the _diff_ window by clicking **Close**. + == Writing your own extension Quarkus extensions add a new developer focused behavior to the core offering, and consist of two distinct parts, buildtime augmentation and runtime container. The augmentation part is responsible for all metadata processing, such as reading annotations, XML descriptors etc. The output of this augmentation phase is recorded bytecode which is responsible for directly instantiating the relevant runtime services. diff --git a/docs/images/break.png b/docs/images/break.png index 787f12f..cab0a65 100644 Binary files a/docs/images/break.png and b/docs/images/break.png differ diff --git a/docs/images/breakreached.png b/docs/images/breakreached.png index b7a8411..4dcce77 100644 Binary files a/docs/images/breakreached.png and b/docs/images/breakreached.png differ diff --git a/docs/images/gitdiff.png b/docs/images/gitdiff.png new file mode 100644 index 0000000..dfeea58 Binary files /dev/null and b/docs/images/gitdiff.png differ diff --git a/docs/images/gitdiffcode.png b/docs/images/gitdiffcode.png new file mode 100644 index 0000000..5c03604 Binary files /dev/null and b/docs/images/gitdiffcode.png differ diff --git a/docs/images/grafclickds.png b/docs/images/grafclickds.png new file mode 100644 index 0000000..ae5aea8 Binary files /dev/null and b/docs/images/grafclickds.png differ diff --git a/docs/images/grafdashsave.png b/docs/images/grafdashsave.png new file mode 100644 index 0000000..53a6a50 Binary files /dev/null and b/docs/images/grafdashsave.png differ diff --git a/docs/images/graffinal.png b/docs/images/graffinal.png index f757de4..45aa921 100644 Binary files a/docs/images/graffinal.png and b/docs/images/graffinal.png differ diff --git a/docs/images/grafgraf.png b/docs/images/grafgraf.png index 0aa5c48..ccdbd29 100644 Binary files a/docs/images/grafgraf.png and b/docs/images/grafgraf.png differ diff --git a/docs/images/grafquery.png b/docs/images/grafquery.png index 5c866e2..4565cc4 100644 Binary files a/docs/images/grafquery.png and b/docs/images/grafquery.png differ diff --git a/docs/images/grafrefresh.png b/docs/images/grafrefresh.png new file mode 100644 index 0000000..e6f62d7 Binary files /dev/null and b/docs/images/grafrefresh.png differ diff --git a/docs/images/prom.png b/docs/images/prom.png index a4ff294..091ac6c 100644 Binary files a/docs/images/prom.png and b/docs/images/prom.png differ diff --git a/docs/images/promg1.png b/docs/images/promg1.png index 1e71152..95cc4a4 100644 Binary files a/docs/images/promg1.png and b/docs/images/promg1.png differ diff --git a/docs/images/promg2.png b/docs/images/promg2.png index 39762ba..9c9355d 100644 Binary files a/docs/images/promg2.png and b/docs/images/promg2.png differ diff --git a/docs/images/promnames.png b/docs/images/promnames.png index 5a7b70e..a0d1ff3 100644 Binary files a/docs/images/promnames.png and b/docs/images/promnames.png differ diff --git a/docs/kafka.adoc b/docs/kafka.adoc index 82abc49..31fb1ba 100644 --- a/docs/kafka.adoc +++ b/docs/kafka.adoc @@ -57,7 +57,7 @@ image::createkafkatopic.png[createkafka,800] We'll need to create a topic for our application to stream to and from, so in the YAML: -* Change the _metadata > names_ value from `my-topic` to `names`. +* Change the _metadata > name_ value from `my-topic` to `names`. * Change the vale of the `strimzi.io/cluster` label from `my-cluster` to `names-cluster` Then click **Create**. @@ -187,13 +187,14 @@ Finally, let’s bind our stream to a JAX-RS resource. Create a new Java class i ---- package org.acme.people.stream; -import io.smallrye.reactive.messaging.annotations.Stream; +import io.smallrye.reactive.messaging.annotations.Channel; import org.reactivestreams.Publisher; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import org.jboss.resteasy.annotations.SseElementType; /** * A simple resource retrieving the in-memory "my-data-stream" and sending the items as server-sent events. @@ -202,19 +203,21 @@ import javax.ws.rs.core.MediaType; public class NameResource { @Inject - @Stream("my-data-stream") Publisher names; // <1> + @Channel("my-data-stream") Publisher names; // <1> @GET @Path("/stream") - @Produces(MediaType.SERVER_SENT_EVENTS) // <2> - public Publisher stream() { // <3> + @Produces(MediaType.SERVER_SENT_EVENTS)// <2> + @SseElementType("text/plain") // <3> + public Publisher stream() { // <4> return names; } } ---- -<1> Injects the `my-data-stream` stream using the `@Stream` qualifier +<1> Injects the `my-data-stream` stream using the `@Channel` qualifier <2> Indicates that the content is sent using _Server Sent Events_ -<3> Returns the stream (Reactive Stream) +<3> Indicates that the data contained within the server sent events is of type `text/plain` +<4> Returns the stream (Reactive Stream) [NOTE] ==== @@ -256,17 +259,17 @@ Add the following values to the `application.properties`: [source,none,role="copypaste"] ---- # Configure the Kafka sink (we write to it) -%prod.mp.messaging.outgoing.generated-name.bootstrap.servers=names-cluster-kafka-bootstrap:9092 +%prod.mp.messaging.outgoing.generated-name.bootstrap.servers=names-cluster-kafka-bootstrap:9092<1> %prod.mp.messaging.outgoing.generated-name.connector=smallrye-kafka %prod.mp.messaging.outgoing.generated-name.topic=names %prod.mp.messaging.outgoing.generated-name.value.serializer=org.apache.kafka.common.serialization.StringSerializer # Configure the Kafka source (we read from it) -%prod.mp.messaging.incoming.names.bootstrap.servers=names-cluster-kafka-bootstrap:9092 +%prod.mp.messaging.incoming.names.bootstrap.servers=names-cluster-kafka-bootstrap:9092<1> %prod.mp.messaging.incoming.names.connector=smallrye-kafka %prod.mp.messaging.incoming.names.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer ---- -We have prefixed these with `%prod` to avoid our app trying to connect when in `dev` or `test` mode. +<1> The hostnames you see here will only make sense (be resolvable via DNS) when this app is run in the same Kubernetes namespace as the Kafka cluster you created earlier. So you'll see this and other config values above prefixed with `%prod` which will not try to initialize Kafka when in `dev` mode. More details about this configuration is available on the https://kafka.apache.org/documentation/#producerconfigs[Producer configuration] and https://kafka.apache.org/documentation/#consumerconfigs[Consumer configuration,window=_blank] section from the Kafka documentation. @@ -301,7 +304,11 @@ If some of them are still starting, you'll need to wait for them! Run the `oc ge == Rebuild Executable JAR -Using the command palette, select **Create Executable JAR**. You should see a bunch of log output that ends with a `SUCCESS` message. +Using the command palette, select **Create Executable JAR**. + +image:createexec.png[create,600] + +You should see a bunch of log output that ends with a `SUCCESS` message. == Deploy to OpenShift diff --git a/docs/messaging.adoc b/docs/messaging.adoc index b4b1e20..19aedbf 100644 --- a/docs/messaging.adoc +++ b/docs/messaging.adoc @@ -89,10 +89,10 @@ With our endpoint, confirm it fails using the Terminal to execute: [source,sh,role="copypaste"] ---- -curl -X POST http://localhost:8080/person/joe +curl -i -X POST http://localhost:8080/person/joe ---- -**This will fail** with an `Internal Server Error`. If you look at the Live Coding terminal, you'll also see the reason: +**This will fail** with an `500 Internal Server Error`. If you look at the Live Coding terminal, you'll also see the reason: [source,none] ---- @@ -122,7 +122,7 @@ import io.quarkus.vertx.ConsumeEvent; @ApplicationScoped public class PersonService { - @ConsumeEvent("add-person") + @ConsumeEvent(value = "add-person", blocking = true) // <1> @Transactional public String addPerson(String name) { LocalDate birth = LocalDate.now().plusWeeks(Math.round(Math.floor(Math.random() * 20 * 52 * -1))); @@ -131,14 +131,15 @@ public class PersonService { p.birth = birth; p.eyes = color; p.name = name; - Person.persist(p); // <1> - return p.name; // <2> + Person.persist(p); // <2> + return p.name; // <3> } } ---- -<1> A new Person entity is created and persisted -<2> The return value of a method annotated with `@ConsumeEvent` is used as response to the incoming message. +<1> By default, the code consuming the event _must_ be non-blocking, as it’s called on the Vert.x event loop. Since our method will block to wait for the transaction, we use `blocking = true` to force this consumer to be run in a _worker thread_. +<2> A new Person entity is created and persisted +<3> The return value of a method annotated with `@ConsumeEvent` is used as response to the incoming message. This bean receives the name, and creates a new `Person` entity and persists it, and then echos back the name (or a well defined failure if things go wrong). @@ -163,10 +164,11 @@ You should get back Joe! { "id": 1004, "birth": "2000-03-15", - "eyes": "BROWN", + "eyes": "BROWN",<1> "name": "joe" } ---- +<1> The eye color you see here may be difference, since it's randomly generated in the `addPerson()` method you added! To better understand, let’s detail how the HTTP request/response has been handled: diff --git a/docs/monitoring.adoc b/docs/monitoring.adoc index 8598913..90c733c 100644 --- a/docs/monitoring.adoc +++ b/docs/monitoring.adoc @@ -161,7 +161,11 @@ Don't forget to import the correct classes as before. == Rebuild Executable JAR -Now we are ready to run our application on the cluster and look at the generated metrics. Using the command palette, select **Create Executable JAR**. You should see a bunch of log output that ends with a `SUCCESS` message. +Now we are ready to run our application on the cluster and look at the generated metrics. Using the command palette, select **Create Executable JAR**. + +image:createexec.png[create,600] + +You should see a bunch of log output that ends with a `SUCCESS` message. == Deploy to OpenShift @@ -206,9 +210,9 @@ If you do not see any `acme` metrics when querying, wait 15 seconds, reload the image:prom.png[Prometheus,800] -These are the metrics exposed by our application, both raw numbers (like number of converted names in the `application:org_acme_people_stream_name_converter_converted_names` metric) along with quantiles of the same data across different time periods (e.g. `application:org_acme_people_stream_name_converter_converter_rate_per_second`). +These are the metrics exposed by our application, both raw numbers (like number of converted names in the `application_org_acme_people_stream_NameConverter_convertedNames_total` metric) along with quantiles of the same data across different time periods (e.g. `application_org_acme_people_stream_NameConverter_converter_rate_per_second`). -Select `application:org_acme_people_stream_name_converter_converted_names` in the box, and click **Execute**. This will fetch the values from our metric showing the number of converted names: +Select `application:application_org_acme_people_stream_NameConverter_convertedNames_total` in the box, and click **Execute**. This will fetch the values from our metric showing the number of converted names: image:promnames.png[names,800] @@ -268,7 +272,13 @@ At the password change prompt, use any password you wish. == Add Prometheus as a data source -You'll land on the Data Source screen. Click **Add Data Source**, and select **Prometheus** as the _Data Source Type_. In the URL box, type `http://prometheus:9090` (this is the hostname and port of our running Prometheus in our namespace): +You'll land on the Home screen. Click **Add Data Source**: + +image:grafclickds.png[names,600] + +and then select **Prometheus** as the _Data Source Type_. + +In the URL box, type `http://prometheus:9090` (this is the hostname and port of our running Prometheus in our namespace): image::grafds.png[datasource, 700] @@ -290,7 +300,11 @@ Click **Add Query**. In the Query box, type `acme` to again get an autocompleted image::grafquery.png[query,600] -Choose the first one `application:org_acme_people_stream_name_converter_converted_names`. The metrics should immediately begin to show in the graph above: +Look for the one ending in `convertedNames_total` and select it. Click the **Refresh** button in the upper right: + +image::grafrefresh.png[query,400] + +The metrics should immediately begin to show in the graph above: image::grafgraf.png[graf,800] @@ -304,15 +318,15 @@ image::grafgen.png[graf,800] There is an _Alerts_ tab you can configure to send alerts (email, etc) when conditions are met for this and other queries. We'll skip this for now. -Click the _Save_ icon at the top to save our new dashboard (you can enter a change comment if you want): +Click the _Save_ icon at the top to save our new dashboard, enter `Quarkus Metrics Dashboard` as its name (you can actually name it any name you want, it will show up in a list of dashboards later on). -image::grafsave.png[graf,800] +image::grafdashsave.png[graf,800] -Give your new dashboard a name and click **Save**. +Click **Save**. == Add more Panels -See if you can add additional Panels. Use the **Add Panel** button to add a new Panel: +See if you can add additional Panels to your new Dashboard. Use the **Add Panel** button to add a new Panel: image::grafmorepanels.png[graf,800] @@ -320,7 +334,7 @@ Follow the same steps as before to create a few more panels, and **don't forget Add Panels for: -* The different quantiles of time it takes to process names `application:org_acme_people_stream_name_converter_converter_seconds` (configure it to _stack_ its values on the _Visualization_ tab, and name it "Converter Performance" on the _General_ tab). +* The different quantiles of time it takes to process names `application_org_acme_people_stream_NameConverter_converter_seconds` (configure it to _stack_ its values on the _Visualization_ tab, and name it "Converter Performance" on the _General_ tab). * The JVM RSS Value `process_resident_memory_bytes` (set the visualization type to `Gauge` and the Field Units to `bytes` on the _Visualization_ tab, and the title to `Memory` on the _General_ tab. image::grafjvm.png[jvm,500] diff --git a/docs/native.adoc b/docs/native.adoc index 280b3e6..5883080 100644 --- a/docs/native.adoc +++ b/docs/native.adoc @@ -48,7 +48,7 @@ We use a profile because, you will see very soon, packaging the native image tak Create a native executable by once again opening the command palette and choose **Build Native Quarkus App**. This will execute `mvn package -Pnative` behind the scenes. The `-Pnative` argument selects the `native` maven profile which invokes the Graal compiler. -**This will take about 3-4 minutes to finish. Wait for it!** +**This will take about 2-3 minutes to finish. Wait for it!** [NOTE] ==== @@ -115,7 +115,7 @@ Nice! == Cleanup -Go to the Terminal in which you ran the native app and press kbd:[CTRL+C] to stop our native app (or close the Terminal window with the `X` button next to its title). +Go to the Terminal in which you ran the native app and press kbd:[CTRL+C] to stop our native app (or close the Terminal window with the `X` button next to its title). **Be sure to leave your Live Coding Terminal open!** == Congratulations! diff --git a/docs/openapi.adoc b/docs/openapi.adoc index a5189e6..6053045 100644 --- a/docs/openapi.adoc +++ b/docs/openapi.adoc @@ -19,10 +19,10 @@ We need to add another extension for OpenAPI. Run the following command: [source,sh,role="copypaste"] ---- -mvn quarkus:add-extension -Dextensions="openapi,swagger" +mvn quarkus:add-extension -Dextensions="openapi" ---- -This will add the necessary extension for using OpenAPI, and a graphical frontend extension called `swagger` which we'll discuss later. It also enables a new RESTful endpoint in the app accessible at `/openapi`. +This will add the necessary extension for using OpenAPI, and a graphical frontend extension called *Swagger* which we'll discuss later. It also enables a new RESTful endpoint in the app accessible at `/openapi`. Access the new endpoint using the following command in a Terminal: diff --git a/docs/panache.adoc b/docs/panache.adoc index a26d292..d352efc 100644 --- a/docs/panache.adoc +++ b/docs/panache.adoc @@ -553,6 +553,8 @@ In previous steps we deployed our sample application as a native binary. Now let Re-build the application as an executable JAR using the command palette and selecting **Create Executable JAR**. +image:createexec.png[create,600] + Next, re-define the container build to use the OpenJDK image using these commands: [source,sh,role="copypaste"] diff --git a/docs/security.adoc b/docs/security.adoc index 2a75b63..b257349 100644 --- a/docs/security.adoc +++ b/docs/security.adoc @@ -20,9 +20,9 @@ Some configuration of the extension is required. Add this to your `application.p [source,properties,role="copypaste"] ---- -mp.jwt.verify.publickey.location={{KEYCLOAK_URL}}/auth/realms/quarkus/protocol/openid-connect/certs # <1> -mp.jwt.verify.issuer={{KEYCLOAK_URL}}/auth/realms/quarkus # <2> -quarkus.smallrye-jwt.auth-mechanism=MP-JWT # <3> +mp.jwt.verify.publickey.location={{KEYCLOAK_URL}}/auth/realms/quarkus/protocol/openid-connect/certs<1> +mp.jwt.verify.issuer={{KEYCLOAK_URL}}/auth/realms/quarkus<2> +quarkus.smallrye-jwt.auth-mechanism=MP-JWT<3> quarkus.smallrye-jwt.realm-name=quarkus quarkus.smallrye-jwt.enabled=true ---- @@ -106,7 +106,11 @@ public class JWTResource { == Rebuild and redeploy app -First, re-build the app using the command palette and selecting **Create Executable JAR**. Once that's done, run the following command to re-deploy: +First, re-build the app using the command palette and selecting **Create Executable JAR**. + +image:createexec.png[create,600] + +Once that's done, run the following command to re-deploy: [source,sh,role="copypaste"] ---- @@ -326,36 +330,35 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; -import org.keycloak.KeycloakSecurityContext; +import io.quarkus.security.identity.SecurityIdentity; -@Path("/secured") +@Path("/secured") // <1> public class KeycloakResource { @Inject - KeycloakSecurityContext keycloakSecurityContext; // <1> + SecurityIdentity identity; // <2> + @GET - @Path("/confidential") // <2> + @Path("/confidential") // <1> @Produces(MediaType.TEXT_PLAIN) public String confidential() { - return ("confidential access for: " + keycloakSecurityContext.getToken().getPreferredUsername() + - " from issuer:" + keycloakSecurityContext.getToken().getIssuer()); + return ("confidential access for: " + identity.getPrincipal().getName() + + " with attributes:" + identity.getAttributes()); } } + ---- -<1> The `KeycloakSecurityContext` is an object produced by the Keycloak extension that you can use to obtain information from tokens sent to your application. -<2> Note that we do not use any `@RolesAllowed` or any other instrumentation on the endpoint to specify access policy. It looks like an ordinary endpoint. - -[NOTE] -==== -There are other APIs you can use if you try to auto-complete the method name using Che, e.g. `getBirthDate()` or `getPicture()`. Place the cursor just after `keycloakSecurityContext.getToken().get` and press kbd:[Ctrl+Space] to see them: - -image::secapis.png[apis, 800] -==== +<1> Note that we do not use any `@RolesAllowed` or any other instrumentation on the endpoint to specify access policy. It looks like an ordinary endpoint. Keycloak (the server) is the one enforcing access here, not Quarkus directly. +<2> The `SecurityIdentity` is a generic object produced by the Keycloak extension that you can use to obtain information about the security principals and attributes embedded in the request. === Rebuild and redeploy app -First, re-build the app using the command palette and selecting **Create Executable JAR**. Once that's done, run the following command to re-deploy: +First, re-build the app using the command palette and selecting **Create Executable JAR**. + +image:createexec.png[create,600] + +Once that's done, run the following command to re-deploy: [source,sh,role="copypaste"] ---- diff --git a/docs/tracing.adoc b/docs/tracing.adoc index be17182..0fc7e55 100644 --- a/docs/tracing.adoc +++ b/docs/tracing.adoc @@ -44,7 +44,7 @@ Like other exercises, we'll need another extension to enable tracing. Install it mvn quarkus:add-extension -Dextensions="opentracing, rest-client" ---- -This will add the necessary entries in your `pom.xml` to bring in the OpenTracing capability, and anm HTTP REST Client we'll use pater. +This will add the necessary entries in your `pom.xml` to bring in the OpenTracing capability, and anm HTTP REST Client we'll use pater. == Configure Quarkus @@ -52,10 +52,10 @@ Next, open the `application.properties` file (in the `src/main/resources` direct [source,none,role="copypaste"] ---- -quarkus.jaeger.service-name=people # <1> -quarkus.jaeger.sampler-type=const # <2> -quarkus.jaeger.sampler-param=1 # <2> -quarkus.jaeger.endpoint=http://jaeger-collector:14268/api/traces # <3> +quarkus.jaeger.service-name=people<1> +quarkus.jaeger.sampler-type=const<2> +quarkus.jaeger.sampler-param=1<2> +quarkus.jaeger.endpoint=http://jaeger-collector:14268/api/traces<3> ---- <1> The name of our service from the perspective of Jaeger (useful when multiple apps report to the same Jaeger instance) <2> How Jaeger samples traces. https://www.jaegertracing.io/docs/1.7/sampling/#client-sampling-configuration[Other options exist,window=_blank] to tune the performance. @@ -65,7 +65,11 @@ quarkus.jaeger.endpoint=http://jaeger-collector:14268/api/traces # <3> Like many other Quarkus frameworks, sensible defaults and out of the box functionality means you can get immediate value out of Quarkus without changing any code. By default, all JAX-RS endpoints (like our `/hello` and others) are automatically traced. Let's see that in action by re-deploying our traced app. -First, re-build the app using the command palette and selecting **Create Executable JAR**. Once that's done, run the following command to re-deploy: +First, re-build the app using the command palette and selecting **Create Executable JAR**. + +image:createexec.png[create,600] + +Once that's done, run the following command to re-deploy: [source,sh,role="copypaste"] ---- @@ -128,7 +132,7 @@ This exercise showa how to use the https://github.com/eclipse/microprofile-rest- We will use the publicly available https://swapi.co[Star Wars API,window=_blank] to fetch some characters from the Star Wars universe. Our first order of business is to setup the model we will be using, in the form of a StarWarsPerson POJO. -=== Create model +=== Create model Create a new class in the `org.acme.people.model` package called `StarWarsPerson` with the following content: @@ -161,7 +165,7 @@ public class StarWarsPerson { This contains a subset of the full Star Wars model, just enough to demonstrate tracing. -=== Create interface +=== Create interface Using the https://github.com/eclipse/microprofile-rest-client[MicroProfile REST Client,window=_blank] is as simple as creating an interface using the proper JAX-RS and MicroProfile annotations. Create a new Java class in the `org.acme.people.service` package called `StarWarsService` with the following content: @@ -258,11 +262,15 @@ public List getCharacters() { <3> For each of the integers, call the `StarWarsService::getPerson` method <4> Collect the results into a list and return it -Don't forget to _Assistant > Organize Imports_ to import the new classes. +**Don't forget to _Assistant > Organize Imports_ **to import the new classes. == Test it out -First, re-build the app using the command palette and selecting **Create Executable JAR**. Once that's done, run the following command to re-deploy: +First, re-build the app using the command palette and selecting **Create Executable JAR**. + +image:createexec.png[create,600] + +Once that's done, run the following command to re-deploy: [source,sh,role="copypaste"] ---- diff --git a/files/quarkus-realm.json b/files/quarkus-realm.json index 64d96a9..c3e230d 100644 --- a/files/quarkus-realm.json +++ b/files/quarkus-realm.json @@ -4,8 +4,8 @@ "notBefore" : 0, "revokeRefreshToken" : false, "refreshTokenMaxReuse" : 0, - "accessTokenLifespan" : 300, - "accessTokenLifespanForImplicitFlow" : 900, + "accessTokenLifespan" : 3000, + "accessTokenLifespanForImplicitFlow" : 9000, "ssoSessionIdleTimeout" : 1800, "ssoSessionMaxLifespan" : 36000, "ssoSessionIdleTimeoutRememberMe" : 0, diff --git a/files/stack.imagestream.yaml b/files/stack.imagestream.yaml index de29a81..40a6136 100644 --- a/files/stack.imagestream.yaml +++ b/files/stack.imagestream.yaml @@ -13,5 +13,5 @@ spec: version: "1.0" from: kind: DockerImage - name: docker.io/schtool/che-quarkus-workshop:0.27.0 + name: docker.io/schtool/che-quarkus-workshop:1.0.0.CR1 name: "1.0" diff --git a/setup/preparelab.sh b/setup/preparelab.sh index a4e6894..8ab51f9 100755 --- a/setup/preparelab.sh +++ b/setup/preparelab.sh @@ -356,4 +356,4 @@ EOF # # then: # DOCKER_BUILDKIT=1 docker build --progress=plain --secret id=rhsm,src=rhsm.secret -t docker.io/username/che-quarkus-workshop:latest -f stack.Dockerfile . -# docker push docker.io/username/che-quarkus-workshop:0.27.0 +# docker push docker.io/username/che-quarkus-workshop:1.0.0.CR1 diff --git a/setup/stack.Dockerfile b/setup/stack.Dockerfile index 1c7cc31..0ca7b6e 100644 --- a/setup/stack.Dockerfile +++ b/setup/stack.Dockerfile @@ -7,6 +7,7 @@ USER root RUN wget -O /tmp/oc.tar.gz https://mirror.openshift.com/pub/openshift-v4/clients/oc/4.1/linux/oc.tar.gz && cd /usr/bin && tar -xvzf /tmp/oc.tar.gz && chmod a+x /usr/bin/oc && rm -f /tmp/oc.tar.gz ENV GRAALVM_VERSION=19.2.1 +ENV QUARKUS_VERSION=1.0.0.CR1 ENV MVN_VERSION=3.6.2 ENV GRAALVM_HOME="/usr/local/graalvm-ce-${GRAALVM_VERSION}" ENV MAVEN_OPTS="-Xmx4G -Xss128M -XX:MetaspaceSize=1G -XX:MaxMetaspaceSize=2G -XX:+CMSClassUnloadingEnabled" @@ -20,9 +21,9 @@ RUN --mount=type=secret,id=rhsm username="$(grep RH_USERNAME /run/secrets/rhsm|c USER jboss -RUN cd /tmp && mkdir project && cd project && mvn io.quarkus:quarkus-maven-plugin:0.27.0:create -DprojectGroupId=org.acme -DprojectArtifactId=footest -Dextensions="quarkus-agroal,quarkus-arc,quarkus-hibernate-orm,quarkus-hibernate-orm-panache,quarkus-jdbc-h2,quarkus-jdbc-postgresql,quarkus-kubernetes,quarkus-scheduler,quarkus-smallrye-fault-tolerance,quarkus-smallrye-health,quarkus-smallrye-opentracing" && mvn -f footest clean compile package && cd / && rm -rf /tmp/project +RUN cd /tmp && mkdir project && cd project && mvn io.quarkus:quarkus-maven-plugin:${QUARKUS_VERSION}:create -DprojectGroupId=org.acme -DprojectArtifactId=footest -Dextensions="quarkus-agroal,quarkus-arc,quarkus-hibernate-orm,quarkus-hibernate-orm-panache,quarkus-jdbc-h2,quarkus-jdbc-postgresql,quarkus-kubernetes,quarkus-scheduler,quarkus-smallrye-fault-tolerance,quarkus-smallrye-health,quarkus-smallrye-opentracing" && mvn -f footest clean compile package && cd / && rm -rf /tmp/project -RUN cd /tmp && mkdir project && cd project && mvn io.quarkus:quarkus-maven-plugin:0.27.0:create -DprojectGroupId=org.acme -DprojectArtifactId=footest -Dextensions="quarkus-smallrye-reactive-streams-operators,quarkus-smallrye-reactive-messaging,quarkus-smallrye-reactive-messaging-kafka,quarkus-swagger-ui,quarkus-vertx,quarkus-kafka-client, quarkus-smallrye-metrics,quarkus-smallrye-openapi" && mvn -f footest clean compile package -Pnative && cd / && rm -rf /tmp/project +RUN cd /tmp && mkdir project && cd project && mvn io.quarkus:quarkus-maven-plugin:${QUARKUS_VERSION}:create -DprojectGroupId=org.acme -DprojectArtifactId=footest -Dextensions="quarkus-smallrye-reactive-streams-operators,quarkus-smallrye-reactive-messaging,quarkus-smallrye-reactive-messaging-kafka,quarkus-swagger-ui,quarkus-vertx,quarkus-kafka-client, quarkus-smallrye-metrics,quarkus-smallrye-openapi" && mvn -f footest clean compile package -Pnative && cd / && rm -rf /tmp/project RUN siege && sed -i 's/^connection = close/connection = keep-alive/' $HOME/.siege/siege.conf && sed -i 's/^benchmark = false/benchmark = true/' $HOME/.siege/siege.conf