<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Rui Zhou on Medium]]></title>
        <description><![CDATA[Stories by Rui Zhou on Medium]]></description>
        <link>https://medium.com/@wirelesser?source=rss-8689b3073a6e------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Y0m71lZsK62t_Ni8TX-Qxg.png</url>
            <title>Stories by Rui Zhou on Medium</title>
            <link>https://medium.com/@wirelesser?source=rss-8689b3073a6e------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 20 Jun 2026 17:16:45 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@wirelesser/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Titanic XGBoost Notebook: Fun with AI-Assisted ROC, Threshold, and EDA]]></title>
            <link>https://medium.com/@wirelesser/titanic-xgboost-notebook-fun-with-ai-assisted-roc-threshold-and-eda-d2ff73ec215f?source=rss-8689b3073a6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/d2ff73ec215f</guid>
            <category><![CDATA[eda]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[kaggle]]></category>
            <category><![CDATA[generative-ai-tools]]></category>
            <category><![CDATA[antigravity]]></category>
            <dc:creator><![CDATA[Rui Zhou]]></dc:creator>
            <pubDate>Sat, 07 Mar 2026 16:30:06 GMT</pubDate>
            <atom:updated>2026-03-07T16:30:06.513Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NtGcJhtJcaI6-THR" /><figcaption>Photo by <a href="https://unsplash.com/@fantasyshao?utm_source=medium&amp;utm_medium=referral">Fantasy Shao</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>The Titanic dataset on Kaggle is a classic machine-learning playground. My notebook largely follows Wissam S.’s excellent step-by-step XGBoost tutorial (<a href="https://www.kaggle.com/code/wissams/titanic-competition-step-by-step-using-xgboost/notebook">Kaggle link</a>).</p><p>This time, I <strong>leveraged AI tools for a bit of fun coding</strong>, quickly adding a few useful features:</p><ul><li><strong>ROC/AUC analysis and threshold tuning</strong> for probability-based evaluation</li><li><strong>Saving and loading the trained model</strong> for reuse</li><li>A <strong>browser-based CSV EDA tool</strong>, inspired by AWS Data Wrangler and DataBrew, that generates instant dataset insights — what took me weeks to code manually a year ago can now be done in minutes.</li></ul><p>Full source code is available here: <a href="https://github.com/softarts/kaggle_titanic_2026">GitHub repository</a>.</p><h3>ROC Curve and Threshold Optimization</h3><p>Instead of using the default threshold of 0.5, I can evaluate <strong>model probabilities</strong> using ROC curves and select a better threshold with Youden’s J statistic:</p><pre>from sklearn.metrics import roc_curve, roc_auc_score<br>import numpy as np<br><br>fpr, tpr, thresholds = roc_curve(y_train, oof_probs)<br>auc_score = roc_auc_score(y_train, oof_probs)<br>best_threshold = thresholds[np.argmax(tpr - fpr)]<br><br>print(&quot;ROC AUC:&quot;, auc_score)<br>print(&quot;Best threshold:&quot;, best_threshold)<br></pre><p>Typical Titanic XGBoost models achieve AUC ≈ 0.85–0.88, with an optimal threshold slightly below 0.5.</p><h3>Saving and Loading the Model</h3><p>To avoid retraining, the model can be saved and loaded easily:</p><pre>import joblib<br><br># Save model<br>joblib.dump(xgb_model, &quot;titanic_xgb_model.pkl&quot;)<br><br># Load model later<br>xgb_model = joblib.load(&quot;titanic_xgb_model.pkl&quot;)</pre><h3>Quick Browser-Based CSV EDA Tool (Inspired by AWS Data Wrangler/DataBrew)</h3><p>Using AI assistants, I built a <strong>lightweight browser-based EDA tool</strong> in just a few minutes — a task that previously took weeks. The design and features are inspired by <strong>AWS Data Wrangler/DataBrew</strong>, but fully client-side and server-free.</p><blockquote>see the eda_implementation_plan.md for the plan</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sQHDjn8t8PuV5lnJypaXIA.png" /></figure><p>These observations guide feature engineering and modeling decisions, just like AWS DataBrew would, but in a lightweight, fully local environment.</p><h3>Takeaways</h3><ul><li><strong>AI-assisted coding makes development fun and fast.</strong></li><li><strong>EDA inspired by AWS Data Wrangler/DataBrew</strong> accelerates dataset understanding.</li><li>Quick <strong>ROC/AUC and threshold tuning</strong> improves probability evaluation.</li><li><strong>Saving/loading models</strong> streamlines experiments and reuse.</li></ul><p>Even small enhancements show how AI can <strong>speed up development and exploration</strong>, letting you focus on learning and experimentation rather than boilerplate code.</p><p><strong><em>Happy Coding!</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d2ff73ec215f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Play Kaggle Titanic with AWS Sagemaker canvas]]></title>
            <link>https://medium.com/@wirelesser/play-kaggle-titanic-with-aws-sagemaker-canvas-4dffbfb7d990?source=rss-8689b3073a6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/4dffbfb7d990</guid>
            <category><![CDATA[sagemaker-canvas]]></category>
            <category><![CDATA[model-training]]></category>
            <category><![CDATA[kaggle]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[aws]]></category>
            <dc:creator><![CDATA[Rui Zhou]]></dc:creator>
            <pubDate>Mon, 21 Oct 2024 16:01:07 GMT</pubDate>
            <atom:updated>2024-10-21T16:01:07.087Z</atom:updated>
            <content:encoded><![CDATA[<h3>Play Kaggle Titanic with AWS Sagemaker Canvas</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IXspklqmd7YXxAWL" /><figcaption>Photo by <a href="https://unsplash.com/@sambalye?utm_source=medium&amp;utm_medium=referral">Sam Balye</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>How to utilize a no-code or low-code environment to create machine learning models without needing to write code</p><h3>Tools for training model</h3><p>There are several options for creating and training machine learning models. While Jupiter Notebook is primarily an interactive coding environment, AWS Sagemaker autopilot can automate the process for users who may have some coding knowledge but want to simplify the modeling process. It automatically prepossesses data, selects algorithms, and tunes hyperparameters.</p><p>Now AWS Sagemaker provides a more advanced platform — canvas, which allows users to build, train, and deploy machine learning models through a no-code or low-code environment. In this article, I will give a step-by-step example of how to utilize a no-code or low-code environment to play kaggle titanic competition without needing to write code.</p><h3>canvas</h3><h4>environment</h4><p>Download the titanic data from <a href="https://www.kaggle.com/competitions/titanic/data">https://www.kaggle.com/competitions/titanic/data</a> , there is train and test data.</p><p>Open AWS Sagemaker canvas workspace (assume you already have AWS account)</p><blockquote>Charge: free tier(first two months) or charge ~1.6$ per hour</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6hci6tHjO87ol5EiI5cUKA.png" /></figure><h4>Import dataset</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AG541DmRlNab6T1EM42SNw.png" /></figure><p>a quick look of dataset</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A_r1IadaIIwOENkkY7ZFtQ.png" /></figure><h4>Model configuration</h4><p>create model, titanic is a predictive — binary task</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DWh9_CGIovkLei0Yr1IYcQ.png" /></figure><p>select survived as target column</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TKsrDygxoQTt29teFk7RJQ.png" /></figure><p>configure model</p><p>Object metric =&gt; “Accuracy”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qkoYEBgUywHiLnwVc7d1fw.png" /></figure><p>Train method and algorithm: choose XGBoost, Linear Models,Random Forest, CatBoost</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d8fwgcDf_7IzBX4dP2zCMQ.png" /></figure><p>Data split =&gt; “80/20”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S6L5kfo54x13THp2ZZ7cPw.png" /></figure><p>Max candidate and runtime =&gt; 1 hour (to save cost)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sqlgsiHaooGWOpRoel13gg.png" /></figure><h4>Build</h4><p>choose standard build</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*k5ujhI9m7Dlj-_sSTzS_Xg.png" /></figure><blockquote>It can’t configure the instance type when training model, if the dataset is big(e.g. train ~5GB data), I got this bill. (use m5.12xlarge type)</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9MUsLAn_aCMRM4g7XfbUrg.png" /></figure><blockquote>For titanic model training, the bill is rather small.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HyQdS-I1cnjBMcfkT5-MHA.png" /></figure><p>Canvas automatically selects appropriate instance types, such as ml.m5.12xlarge, ml.c5.18xlarge, and ml.m5.4xlarge, based on the dataset size, performance, and availability. Refer to the <a href="https://aws.amazon.com/sagemaker/pricing/">SageMaker instance pricing</a> page for more information.</p><p>model is ready after ~20 minutes, we can see some model information</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*khW3QBY9rYnYFgl-jIV4IQ.png" /></figure><p>and</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rUvQS5OJDSWkk6XcTVbaYw.png" /></figure><p>and metrics</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FKHAWw-8N1SXSflR7Rf9JQ.png" /></figure><h4>Predict</h4><p>Now we will try to predict on titanic test data.</p><p>Import titanic test data, then click predict, select the titanic-test.csv data</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*66GsiKHPwnrx4-xhmtzl0g.png" /></figure><p>download the predict result(saved to aws-sagemaker-predict.csv)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Lb3nF55neepUHlM0l5BVPA.png" /></figure><p>convert to kaggle format (notebook or any IDE)</p><pre>import pandas as pd <br>prediction = pd.read_csv(&#39;./aws-sagemaker-predict.csv&#39;)<br>test_data = pd.read_csv(&#39;./test.csv&#39;)<br>output = pd.DataFrame({&#39;PassengerId&#39;: test_data.PassengerId, &#39;Survived&#39;: prediction.Survived})<br>output.to_csv(&quot;aws_sagemaker_submission.csv&quot;,index=False)</pre><p>login to kaggle and submit the result</p><p>It is not bad vs my previous submission(0.79186)</p><p>another highly optimized one is 0.79904 (<a href="https://www.kaggle.com/code/wissams/titanic-competition-step-by-step-using-xgboost">https://www.kaggle.com/code/wissams/titanic-competition-step-by-step-using-xgboost</a>)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*99ZozZPdhfUAsgBo31iLgA.png" /></figure><h4>canvas status</h4><p>we can check canvas status, e.g what processing jobs it has run.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jRV0-YH1JwheFI1hUeu8vQ.png" /></figure><h3>Multiple models</h3><p>let’s create a second model</p><p>select hyperparameter optimization</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0YiyCM9XklpjGS5PcOauqQ.png" /></figure><p>set version 2</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*j2cQ270t3A6Q3wiSkV0uFQ.png" /></figure><p>model status</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xMY7rieVogVJ65sEze0fhA.png" /></figure><p>model leaderboard — select the best one</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lmLsDEbb6Usfx6u2QQ9wHA.png" /></figure><p>and compare the model version</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vUjxXcM9wj9V8FHQXlHGOA.png" /></figure><h3>Deploy model</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*azI0cbpz5snIaJN6j2uACw.png" /></figure><p>it can select the deployment instance type. e.g. ml.m5.xlarge4 has 16 GiB, and charge $0.23 per hour. it is included in free-tier sagemaker.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S3soiRKg5GbdNwkl2FfMmg.png" /></figure><p>deployment url</p><p><a href="https://runtime.sagemaker.ap-southeast-1.amazonaws.com/endpoints/canvas-m5xlarge/invocations">https://runtime.sagemaker.ap-southeast-1.amazonaws.com/endpoints/canvas-m5xlarge/invocations</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MygcvtOqSbzeQpfzrjzENg.png" /></figure><p>test client</p><p>Sagemaker canvas provides a UI to test the endpoint, it can also use API gateway and PythonSDK to access the model deployment endpoint.</p><ul><li><a href="https://docs.aws.amazon.com/sagemaker/latest/dg/realtime-endpoints-test-endpoints.html">Invoke models for real-time inference</a></li><li><a href="https://aws.amazon.com/blogs/machine-learning/creating-a-machine-learning-powered-rest-api-with-amazon-api-gateway-mapping-templates-and-amazon-sagemaker/">Creating a machine learning-powered REST API with Amazon API Gateway mapping templates and Amazon SageMaker | Amazon Web Services</a></li></ul><h3>Reference</h3><p><a href="https://aws.amazon.com/blogs/machine-learning/build-and-evaluate-machine-learning-models-with-advanced-configurations-using-the-sagemaker-canvas-model-leaderboard/">Build and evaluate machine learning models with advanced configurations using the SageMaker Canvas model leaderboard | Amazon Web Services</a></p><p><strong><em>Happy coding!</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4dffbfb7d990" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Implement API first strategy with OpenAPI generator plugin]]></title>
            <link>https://medium.com/javarevisited/implement-api-first-strategy-with-openapi-generator-plugin-e4bbe7f0d778?source=rss-8689b3073a6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/e4bbe7f0d778</guid>
            <category><![CDATA[openapi-specification]]></category>
            <category><![CDATA[openapi-generator]]></category>
            <category><![CDATA[openapi-generator-plugin]]></category>
            <category><![CDATA[api-first-development]]></category>
            <category><![CDATA[maven-plugin]]></category>
            <dc:creator><![CDATA[Rui Zhou]]></dc:creator>
            <pubDate>Mon, 08 Apr 2024 15:35:06 GMT</pubDate>
            <atom:updated>2024-04-08T15:35:06.292Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*G6pjaAPxtEpaQyeG" /><figcaption>Photo by <a href="https://unsplash.com/@juniperphoton?utm_source=medium&amp;utm_medium=referral">JuniperPhoton</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Everyone is talking about API first strategy nowadays, In this article I will demo how we exactly implement API first strategy with OpenAPI generator plugin in a real-world solution.</p><h3>Strategy</h3><p>API-first, also called the API-first approach, prioritizes APIs at the beginning of the software development process, positioning APIs as the building blocks of software. API-first organizations develop APIs before writing other code, instead of treating them as afterthoughts. This lets teams construct applications with internal and external services that are delivered through APIs.</p><p>Advantages of the API-first approach with a common OpenAPI Specification:</p><ul><li>Ensures the use of the same contracts by all the parts of the system.</li><li>Facilitates cooperation between the backend and the frontend, facilitating parallel development.</li><li>Automates generation of repeatable elements like models, services and controllers, reducing the amount of boilerplate code, organizing the structure and saving developers’ time.</li><li>Automatically generates and enables API documentation.</li></ul><h3>Diagram</h3><figure><img alt="API first approach" src="https://cdn-images-1.medium.com/max/1024/1*7Cn0XgSh2uAVLtu73omMMg.png" /></figure><p>As seen from this picture, before developing the application, at the API design stage, we will create OpenAPI spec(e.g. a yaml file) and store it as a single source in the git repo. At this stage, the API designer, such as architect, BA, developer, they need to contribute to this API spec document and do a proper review, then push it to git repo and trigger the CICD process.</p><p>CICD will validate the API spec, if the spec is incompatible with the previous version then we need to create a newer version and commit again. otherwise, the spec will be uploaded to an artifact store such as Jfrog Artifactory or Nexus. In this demo, I don’t have artifactory, hence I use bitbucket as the storage instead.</p><p>At the microservice development stage, developer will use the openapi generator plugin(maven/gradle), to import the API spec from remote url(e.g. bitbucket or jfrog artifactort), and generate server/client code from the API spec.</p><p>We can also customize the openapi generator plugin, parse the api spec configuration to export API dependency information. such as:</p><ul><li>microservice A has dependency of microservice B v1 api, microservice C v2 api</li><li>microservice B has dependency of microservice C v3 api</li></ul><p>with these information we can create application catalog, inventory, … etc.</p><h3>Generate code with the OpenAPI generator plugin</h3><p>When a microservice wants to create an API server/client, just needs to have this configuration in its maven POM.xml:</p><pre>&lt;build&gt;        <br>    &lt;plugins&gt;        <br>        &lt;plugin&gt;<br>            &lt;groupId&gt;org.openapitools&lt;/groupId&gt;<br>            &lt;artifactId&gt;openapi-generator-maven-plugin&lt;/artifactId&gt;<br>            &lt;version&gt;${openapi-generator.version}&lt;/version&gt;<br>            &lt;executions&gt;<br>                &lt;execution&gt;<br>                    &lt;id&gt;petstore-client&lt;/id&gt;<br>                    &lt;goals&gt;<br>                        &lt;goal&gt;generate&lt;/goal&gt;<br>                    &lt;/goals&gt;<br>                    &lt;configuration&gt;<br>                        &lt;auth&gt;&lt;/auth&gt;<br>                        &lt;!--                            &lt;inputSpec&gt;${project.basedir}/src/main/resources/api.yaml&lt;/inputSpec&gt;--&gt;<br>                        &lt;inputSpec&gt;https://raw.githubusercontent.com/softarts/apifirst/main/apifirst-contracts/retail/petstore/v1.yaml&lt;/inputSpec&gt;<br>                        &lt;generatorName&gt;java&lt;/generatorName&gt;<br>                        &lt;skipIfSpecIsUnchanged&gt;true&lt;/skipIfSpecIsUnchanged&gt;<br>                        &lt;generateApiTests&gt;false&lt;/generateApiTests&gt;<br>                        &lt;generateModelTests&gt;false&lt;/generateModelTests&gt;<br>&lt;!--                            &lt;generateSupportingFiles&gt;false&lt;/generateSupportingFiles&gt;--&gt;<br>                        &lt;configOptions&gt;<br>&lt;!--                                &lt;sourceFolder&gt;src/gen/java/main&lt;/sourceFolder&gt;--&gt;<br>                            &lt;interfaceOnly&gt;true&lt;/interfaceOnly&gt;<br>                            &lt;dateLibrary&gt;java8&lt;/dateLibrary&gt;<br>                            &lt;delegatePattern&gt;true&lt;/delegatePattern&gt;<br>                            &lt;useTags&gt;true&lt;/useTags&gt;<br>                            &lt;requestMappingMode&gt;api_interface&lt;/requestMappingMode&gt;<br>                            &lt;library&gt;resttemplate&lt;/library&gt;<br>                            &lt;generateClientAsBean&gt;true&lt;/generateClientAsBean&gt;<br>                            &lt;useSpringBoot3&gt;true&gt;&lt;/useSpringBoot3&gt;<br>                            &lt;useJakartaEe&gt;true&lt;/useJakartaEe&gt;<br>                            &lt;apiPackage&gt;com.zhourui.retail.petstore.consumer.api.v1&lt;/apiPackage&gt;<br>                            &lt;modelPackage&gt;com.zhourui.retail.petstore.model.v1&lt;/modelPackage&gt;<br>                            &lt;serializableModel&gt;true&lt;/serializableModel&gt;<br>                        &lt;/configOptions&gt;<br>                    &lt;/configuration&gt;<br>                &lt;/execution&gt;<br>                &lt;execution&gt;<br>                    &lt;id&gt;petstore-server&lt;/id&gt;<br>                    &lt;goals&gt;<br>                        &lt;goal&gt;generate&lt;/goal&gt;<br>                    &lt;/goals&gt;<br>                    &lt;configuration&gt;<br>                        &lt;!--                            &lt;inputSpec&gt;${project.basedir}/src/main/resources/api.yaml&lt;/inputSpec&gt;--&gt;<br>                        &lt;auth&gt;&lt;/auth&gt;<br>                        &lt;inputSpec&gt;https://raw.githubusercontent.com/softarts/apifirst/main/apifirst-contracts/retail/petstore/v1.yaml&lt;/inputSpec&gt;<br>                        &lt;generatorName&gt;spring&lt;/generatorName&gt;<br>                        &lt;skipIfSpecIsUnchanged&gt;true&lt;/skipIfSpecIsUnchanged&gt;<br>                        &lt;configOptions&gt;<br>                            &lt;!--                                &lt;sourceFolder&gt;src/gen/java/main&lt;/sourceFolder&gt;--&gt;<br>                            &lt;validateSpec&gt;false&lt;/validateSpec&gt;<br>                            &lt;interfaceOnly&gt;true&lt;/interfaceOnly&gt;<br>                            &lt;dateLibrary&gt;java8&lt;/dateLibrary&gt;<br>                            &lt;delegatePattern&gt;false&lt;/delegatePattern&gt;<br>                            &lt;useTags&gt;true&lt;/useTags&gt;<br>                            &lt;requestMappingMode&gt;api_interface&lt;/requestMappingMode&gt;<br>                            &lt;generateClientAsBean&gt;true&lt;/generateClientAsBean&gt;<br>                            &lt;useSpringBoot3&gt;true&gt;&lt;/useSpringBoot3&gt;<br>                            &lt;useJakartaEe&gt;true&lt;/useJakartaEe&gt;<br>                            &lt;apiPackage&gt;com.zhourui.retail.petstore.producer.api.v1&lt;/apiPackage&gt;<br>                            &lt;modelPackage&gt;com.zhourui.retail.petstore.model.v1&lt;/modelPackage&gt;<br>                        &lt;/configOptions&gt;<br>                    &lt;/configuration&gt;<br>                &lt;/execution&gt;<br>            &lt;/executions&gt;<br>        &lt;/plugin&gt;<br>    &lt;/plugins&gt;<br>&lt;/build&gt;</pre><p>The full parameters list are at the <a href="https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-maven-plugin">openapi repo</a>. here I just highlight some important stuff:</p><h4>API spec</h4><p>it use github repo to store the API spec(see <a href="https://raw.githubusercontent.com/softarts/apifirst/main/apifirst-contracts/retail/petstore/v1.yaml">here</a>), and this is the single source of API spec. all participants need to create their code from this spec.</p><pre>&lt;inputSpec&gt;https://raw.githubusercontent.com/softarts/apifirst/main/apifirst-contracts/retail/petstore/v1.yaml&lt;/inputSpec&gt;</pre><h4>&lt;auth&gt;</h4><p>This is the auth header when need to access API spec file which resides at remote url(e.g. different bitbucket repo). First of all, create a personal token at bitbucket account management; then encode it in base64.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/996/1*RPpUIbIh1vc0T2oNaecvbg.png" /></figure><pre>bitbucket_username:bitbucket_token<br>=&gt; base64 encode<br>Yml0YnVja2V0X3VzZXJuYW1lOmJpdGJ1Y2tldF90b2tlbg==</pre><p>the &lt;auth&gt; will be:</p><pre>&lt;auth&gt;Authorization:Basic Yml0YnVja2V0X3VzZXJuYW1lOmJpdGJ1Y2tldF90b2tlbg==&lt;/auth&gt;</pre><h4>multi languages</h4><p>openapi generator supports <a href="https://openapi-generator.tech/docs/generators/">multiple languages</a>, usually I will use</p><ul><li>java(generator) resttemplate(library) =&gt; java-client, use resttemplate as http client</li><li>kotlin-spring(generator) =&gt; kotlin-spring server</li></ul><h3>Implement service with few lines of code</h3><p>OpenAPI generator maven plugin will generate model, consumer, producer API files under the target folder.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jyWd-xE6waGh0rlLalfCuQ.png" /></figure><h4>controller(server)</h4><p>Just implements from the generated API(StoreApi ) and copy the function declaration:</p><pre>import com.zhourui.retail.petstore.producer.api.v1.StoreApi;<br><br>@RestController<br>public class StoreController implements StoreApi {<br>    @Override<br>    public ResponseEntity&lt;Map&lt;String, Integer&gt;&gt; getInventory() {<br>        // your code<br>        Map&lt;String, Integer&gt; m = Map.of(<br>                &quot;sugar&quot;, 1000,<br>                &quot;wheat&quot;, 250<br>        );<br>        return new ResponseEntity&lt;&gt;(m, HttpStatus.OK);<br>    }<br>}</pre><h4>client</h4><p>use bean configuration to set server url</p><pre>@Configuration<br>@Import(<br>        value = {StoreApi.class, com.zhourui.retail.petstore.consumer.api.ApiClient.class}<br>)<br>public class BaseConfiguration {<br>    @Value(&quot;${ApiFirstDemo.API.PetStoreUrl}&quot;)<br>    private String petStoreUrl;<br><br>    @Bean<br>    public RestTemplate restTemplate() {<br>        return new RestTemplate();<br>    }<br><br>    @Bean<br>    public StoreApi storeApi() {<br>        StoreApi storeApi = new StoreApi();<br>        storeApi.getApiClient().setBasePath(petStoreUrl);<br>        return storeApi;<br>    }<br>}</pre><p>the URL is in application.yaml (use default value, in production it can be rendered by CICD)</p><pre>ApiFirstDemo:<br>  API:<br>    PetStoreUrl: ${PetStoreUrlV1:http://localhost:8080/api/v3}</pre><p>and usage</p><pre>@RestController<br>public class DemoController {<br>    @Autowired<br>    private StoreApi storeApi;<br><br>    @GetMapping(value = &quot;/clientdemo&quot;)<br>    public ResponseEntity&lt;Map&lt;String, Integer&gt;&gt; clientDemo() {<br>        Map&lt;String, Integer&gt; m = storeApi.getInventory();<br>        return new ResponseEntity&lt;&gt;(m,OK);<br>    }<br>}</pre><h4>Testing</h4><pre>curl -X GET &quot;http://localhost:8080/clientdemo&quot;<br># =&gt; will hit its own endpoint &quot;storeapi&quot;<br>{&quot;wheat&quot;:250,&quot;sugar&quot;:1000}</pre><h3>Reference</h3><ul><li><a href="https://www.j-labs.pl/en/tech-blog/api-first-with-open-api-generator/">https://www.j-labs.pl/en/tech-blog/api-first-with-open-api-generator/</a></li><li><a href="https://www.baeldung.com/spring-boot-openapi-api-first-development">https://www.baeldung.com/spring-boot-openapi-api-first-development</a></li><li>source code is at <a href="https://github.com/softarts/apifirst">github</a></li></ul><p><strong><em>Happy Coding!</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e4bbe7f0d778" width="1" height="1" alt=""><hr><p><a href="https://medium.com/javarevisited/implement-api-first-strategy-with-openapi-generator-plugin-e4bbe7f0d778">Implement API first strategy with OpenAPI generator plugin</a> was originally published in <a href="https://medium.com/javarevisited">Javarevisited</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spring OAuth2 client— integrate with social login]]></title>
            <link>https://medium.com/javarevisited/spring-oauth2-client-integrate-with-social-login-3ad3de9df8a7?source=rss-8689b3073a6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/3ad3de9df8a7</guid>
            <category><![CDATA[social-login]]></category>
            <category><![CDATA[google]]></category>
            <category><![CDATA[oauth-authentication]]></category>
            <category><![CDATA[oauth2]]></category>
            <category><![CDATA[spring-security]]></category>
            <dc:creator><![CDATA[Rui Zhou]]></dc:creator>
            <pubDate>Wed, 28 Feb 2024 14:43:55 GMT</pubDate>
            <atom:updated>2024-03-01T16:36:37.623Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="GuangZhou, PRC" src="https://cdn-images-1.medium.com/max/1024/0*gfRxA7CiN6ATaYf5" /><figcaption>Photo by <a href="https://unsplash.com/@okcdz?utm_source=medium&amp;utm_medium=referral">Vincent Chan</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In this article, I will give an example of how to use Spring OAuth2 client to integrate with social login with just a few lines of kotlin code and configuration.</p><p>See the below diagram, Google/Facebook account service is the Authorization Server. When they receive Auth Code request from a client application, they will redirect them to login page, end user will complete the social login on the page and will be redirected back to the original login page.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/654/1*IwovWCKMRDHcVba_amjy7w.png" /><figcaption><a href="https://www.baeldung.com/spring-security-oauth-resource-server">https://www.baeldung.com/spring-security-oauth-resource-server</a></figcaption></figure><h3>Setup Google OAuth2 Client</h3><p>First of all, goto <a href="https://console.cloud.google.com/apis/dashboard?pli=1">https://console.cloud.google.com/apis/dashboard?pli=1</a>, we need to create new project, e.g. spring-oauth2-client</p><p>then we add Credentials and OAuth consent screen</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0jfyV1N1e9ND-Of4dkqpvA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/868/1*7Mt02f1QCCSE2DHSLhV7pg.png" /></figure><p>Remember that We also have to configure an authorized redirect URI in the Google Console, which is the path that users will be redirected to after they successfully log in with Google.</p><p>By default, Spring Boot configures this redirect URI as <em>/login/oauth2/code/{registrationId}</em>.</p><p>So, for Google we’ll add this URI:</p><pre>http://localhost:8081/login/oauth2/code/google</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wUmQ_fQ61HfM6abHcDRkTg.png" /></figure><h3>Spring Boot Configuration</h3><p>dependency in build.gradle</p><pre>implementation(&quot;org.springframework.boot:spring-boot-starter&quot;)<br>implementation(&quot;org.springframework.boot:spring-boot-starter-web&quot;)<br>implementation(&quot;org.springframework.boot:spring-boot-starter-security&quot;)<br>implementation(&quot;org.springframework.boot:spring-boot-starter-oauth2-client&quot;)</pre><p>See <a href="https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java">org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java</a>, Spring-boot-starter-oauth2-client has built-in support for OAuth2 client Google, Github, Facebook and Okta.</p><pre>public enum CommonOAuth2Provider {<br><br> GOOGLE {<br><br>  @Override<br>  public Builder getBuilder(String registrationId) {<br>   ClientRegistration.Builder builder = getBuilder(registrationId,<br>     ClientAuthenticationMethod.CLIENT_SECRET_BASIC, DEFAULT_REDIRECT_URL);<br>   builder.scope(&quot;openid&quot;, &quot;profile&quot;, &quot;email&quot;);<br>   builder.authorizationUri(&quot;https://accounts.google.com/o/oauth2/v2/auth&quot;);<br>   builder.tokenUri(&quot;https://www.googleapis.com/oauth2/v4/token&quot;);<br>   builder.jwkSetUri(&quot;https://www.googleapis.com/oauth2/v3/certs&quot;);<br>   builder.issuerUri(&quot;https://accounts.google.com&quot;);<br>   builder.userInfoUri(&quot;https://www.googleapis.com/oauth2/v3/userinfo&quot;);<br>   builder.userNameAttributeName(IdTokenClaimNames.SUB);<br>   builder.clientName(&quot;Google&quot;);<br>   return builder;<br>  }<br><br> }<br>// xxxx</pre><p>application.yaml</p><pre>spring:<br>  security:<br>    oauth2:<br>      client:<br>        registration:          <br>          google:<br>            client-id: your_client_id<br>            client-secret: your_client_secret<br><br>          mykeycloak:<br>            client-id: login-app<br>            authorization-grant-type: authorization_code<br>            scope: openid<br>        provider:<br>          mykeycloak:<br>            issuer-uri: http://localhost:8080/auth/realms/SpringBootKeycloak<br>            user-name-attribute: preferred_username</pre><p>Add the above configurations under oauth2.client.registration in spring boot profile will enable the Oauth2ClientAutoConfiguration<em> </em>and create the necessary beans. We can also add Facebook or our customized client mykeycloak</p><blockquote>to setup keycloak authorization server, refer to this <a href="https://medium.com/@wirelesser/oauth2-write-a-resource-server-with-keycloak-and-spring-security-c447bbca363c">https://medium.com/@wirelesser/oauth2-write-a-resource-server-with-keycloak-and-spring-security-c447bbca363c</a></blockquote><h3>Testing</h3><p>Spring OAuth2 client will automatically set /login as the default login page, all other requests need to be authenticated.</p><p>Start the application at 8081 port, and input <a href="http://localhost:8081/">http://localhost:8081/ </a>in the browser, it will be routed to the default login page because it is not authenticated yet.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ebL4BD4a55CPVmjL2CZt6A.png" /></figure><p>=&gt;</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jFjhseBuSzHNNh3tLdUydg.png" /></figure><p>after login successfully it will be routed to the default home page /</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AmwxYsienxrmqs-fzS7-yQ.png" /></figure><p>logout is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CjdSLqi4E1GskkBo72XWow.png" /></figure><h4>Start independently</h4><p>Note: It must start keycloak server first because spring boot oauth2 client will try to fetch oauth2 client configuration information from keycloak server.</p><pre>Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name &#39;clientRegistrationRepository&#39; defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.class]: Failed to instantiate [org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository]: Factory method &#39;clientRegistrationRepository&#39; threw exception with message: Unable to resolve Configuration with the provided Issuer of &quot;http://localhost:8080/auth/realms/SpringBootKeycloak&quot;</pre><p>If we need to start it independently, then we can supply the <em>jwk-set-uri</em> property instead to point to the authorization server’s endpoint exposing public keys:</p><pre>spring:<br>  security:<br>    oauth2:<br>      client:<br>        registration:<br>          google:<br>            client-id: 1043125932604-sq96atrmiserm85fds8apsimbpgg40dv.apps.googleusercontent.com<br>            client-secret: your_google_secret<br><br>          facebook:<br>            client-id: your_facebook_id<br>            client-secret: your_facebook_secret<br><br>          mykeycloak:<br>            client-id: login-app<br>            authorization-grant-type: authorization_code<br>            scope: openid<br>            redirectUri: http://localhost:8081/login/oauth2/code/mykeycloak<br><br>        provider:<br>          mykeycloak:  <br>            user-name-attribute: preferred_username<br>            jwk-set-uri: http://localhost:8080/auth/realms/SpringBootKeycloak/protocol/openid-connect/certs<br>            authorization-uri: http://localhost:8080/auth/realms/SpringBootKeycloak/protocol/openid-connect/auth<br>            token-uri: http://localhost:8080/auth/realms/SpringBootKeycloak/protocol/openid-connect/token<br>            user-info-uri: http://localhost:8080/auth/realms/SpringBootKeycloak/protocol/openid-connect/userinfo</pre><h3>Reference</h3><ul><li><a href="https://www.baeldung.com/spring-security-5-oauth2-login">https://www.baeldung.com/spring-security-5-oauth2-login</a></li></ul><p><a href="https://docs.spring.io/spring-authorization-server/reference/guides/how-to-social-login.html">How-to: Authenticate using Social Login</a></p><ul><li>source code at <a href="https://github.com/softarts/oauth2client">github</a></li></ul><p><strong><em>Happy Coding!</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3ad3de9df8a7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/javarevisited/spring-oauth2-client-integrate-with-social-login-3ad3de9df8a7">Spring OAuth2 client— integrate with social login</a> was originally published in <a href="https://medium.com/javarevisited">Javarevisited</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[OAuth2 — write a resource server with KeyCloak and Spring Security]]></title>
            <link>https://medium.com/@wirelesser/oauth2-write-a-resource-server-with-keycloak-and-spring-security-c447bbca363c?source=rss-8689b3073a6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/c447bbca363c</guid>
            <category><![CDATA[resource-server]]></category>
            <category><![CDATA[oauth2]]></category>
            <category><![CDATA[keycloak]]></category>
            <category><![CDATA[jwt]]></category>
            <category><![CDATA[spring-security]]></category>
            <dc:creator><![CDATA[Rui Zhou]]></dc:creator>
            <pubDate>Sat, 03 Feb 2024 15:41:35 GMT</pubDate>
            <atom:updated>2024-02-03T15:41:35.516Z</atom:updated>
            <content:encoded><![CDATA[<h3>OAuth2 — write a resource server with KeyCloak and Spring Security</h3><p>Nowadays in a Cloud-native application system, a microservice is a kind of resource server that needs to be protected. In this article, I will give an example resource server protected by Spring Security 6(Spring Boot 3) and Authorization Server(KeyCloak)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/780/1*ATaX8OlFh2ZEr1Exys2mWg.png" /></figure><h3>Authorization Server</h3><p>There was an Authorization Server class in Spring Security OAuth2, but now it is deprecated.</p><p><a href="https://stackoverflow.com/questions/59273338/what-is-the-replacement-for-the-deprecated-authorizationserver-in-spring-securit">https://stackoverflow.com/questions/59273338/what-is-the-replacement-for-the-deprecated-authorizationserver-in-spring-securit</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/938/1*0OmF5quKnx4G9hIlvX9zmA.png" /></figure><p>Then they announced a new Authorization Server at <a href="https://github.com/spring-projects-experimental/spring-authorization-server">https://github.com/spring-projects-experimental/spring-authorization-server</a></p><p><a href="https://spring.io/blog/2020/04/15/announcing-the-spring-authorization-server">Announcing the Spring Authorization Server</a></p><p>If you want to write your own Authorization Server, ensure you use the correct package and dependency.</p><h3>KeyCloak</h3><p>We can also leverage some open-source projects, such as Keycloak and Forgerock to run as authorization server. For more details refer to <a href="https://www.liventerprise.com/compare/ForgeRock_vs_Keycloak/">here</a>. Both of them provide similar IAM features, such as authentication, authorization, single sign-on (SSO), social media logins, multi-factor authentication (MFA), and user self-service. However, the specific implementations and capabilities may differ between the two solutions. ForgeRock is known for its comprehensive and enterprise-ready IAM platform with extensive customization and integration options, while KeyCloak is known for its simplicity and ease of use, making it popular for smaller-scale deployments and developer-friendly use cases. Keycloak has an active community of users and contributors but does not offer commercial support options.</p><p>In this case, we use keycloak as an authorization server</p><p>docker-compose.yaml file</p><pre>version: &#39;3.8&#39;<br>services:<br>  keycloak:<br>    image: jboss/keycloak<br>    restart: always<br>    environment:<br>      KEYCLOAK_USER: admin<br>      KEYCLOAK_PASSWORD: admin<br>    ports:<br>      - &quot;8080:8080&quot;<br>    networks:<br>      backend:<br>        aliases:<br>          - &quot;keycloak&quot;<br>    volumes:<br>      #- keycloak_data:/data<br>      - keycloak_standalone_data:/opt/jboss/keycloak/standalone/data/<br>      - keycloak_data:/opt/keycloak/data/<br><br>volumes:<br>  db:<br>    driver: local<br>  keycloak_data:<br>  keycloak_standalone_data:<br><br><br>networks:<br>  backend:<br>    driver: bridge</pre><p>volume mapping will store the change in local volume.</p><p>start:</p><pre>docker-compose up</pre><h4>Configuration</h4><p><a href="http://localhost:8080/auth/">http://localhost:8080/auth/</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w7Y5_UyI5UTNKE9GNIJXsA.png" /></figure><p>click administration console and username/password is admin/admin</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/820/1*24GBIG_cIF_OWaPu54IlQA.png" /></figure><ul><li>Add Realm</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qgyK8Nni4Y_e3qaI9F1mQQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OAo2S50sbtyleb0t36MxIg.png" /></figure><p>test the realm</p><pre>curl http://localhost:8080/auth/realms/SpringBootKeycloak<br>{&quot;realm&quot;:&quot;SpringBootKeycloak&quot;,&quot;public_key&quot;:&quot;MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqbGRyZ+LkEykTBzKzqCwLxYYlkzipAGfGzlm58L2rhAlMBgBYakwHyZGMXhzA3mNO6oSlKb2NziWC5pHjFwyrt5rOIxPJK5M0zGSORPVfeQ7okOjX0ja6ozSjXXdLr8Zg3Xo/TRlYS5IzZCtyX/jmtR92wRwSTJnWhFcaenqs22A0Cle94/mK6nyMJvLkb/YWdpeeEQZekFVQ7QYhKmAWD91YkVQxh+tLkapAHh9yTvBI3fRfpzYTJKFpV5wBBUy6qbBksHWBw8EHdaOrJZ6QXcnChCNb/VNLyYtKoclS/JmW5tkLj2slPy4CA20P2qY87qykzuq+8BSC/5FDv33FwIDAQAB&quot;,&quot;token-service&quot;:&quot;http://localhost:8080/auth/realms/SpringBootKeycloak/protocol/openid-connect&quot;,&quot;account-service&quot;:&quot;http://localhost:8080/auth/realms/SpringBootKeycloak/account&quot;,&quot;tokens-not-before&quot;:0}</pre><p>switch to the newly created realm</p><ul><li>Create Client</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Df-fsx_aJkWQ5R5khDa7Gw.png" /></figure><p>will show =&gt;</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*T9WTiAKcOQf_YPSW0jaQjA.png" /></figure><ul><li>Create user user1</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NR-nQjM8Y77IzFUznoCnIQ.png" /></figure><p>=&gt;</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*25iOuPFS-5jLzb-_qm6Acw.png" /></figure><p>set password to pass1</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IPQOzqUF6kfn4-G8KHh1fg.png" /></figure><p>remove required user action update password</p><ul><li>Create Role</li></ul><p>I created a role name “user”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wzmXzvq4rZq1_SRxANr1zg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_MRsbMDGYO6A_6kPNRHdPA.png" /></figure><p>assign the role</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U86VTASZYFy_G5HjNGQXdQ.png" /></figure><ul><li>get configuration</li></ul><p><a href="http://localhost:8080/auth/realms/SpringBootKeycloak/.well-known/openid-configuration">http://localhost:8080/auth/realms/SpringBootKeycloak/.well-known/openid-configuration</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/978/1*jo9OEeGmEsysifCDZUQiyQ.png" /></figure><p>in this example we mainly use the token_endpoint</p><h3>Resource Server</h3><p>I use SpringBoot to implement a Resource Server application.</p><h4>Security Configuration</h4><p>The KeyCloak adapter is not used in Spring boot 3, see this =&gt;</p><p><a href="https://stackoverflow.com/questions/74571191/use-keycloak-spring-adapter-with-spring-boot-3/74572732#74572732">Use Keycloak Spring Adapter with Spring Boot 3</a></p><p>and this =&gt;</p><p><a href="https://www.keycloak.org/2023/03/adapter-deprecation-update">Update on deprecation of Keycloak adapters</a></p><p>enable OAuth2 resource server in <a href="https://github.com/softarts/resource-server/blob/main/src/main/kotlin/com/tower/resourceserver/config/SecurityConfig.kt">SecurityConfig </a>file:</p><pre>http.oauth2ResourceServer {<br>    oauth2: OAuth2ResourceServerConfigurer&lt;HttpSecurity?&gt; -&gt;<br>    oauth2.jwt(<br>        Customizer { jwt -&gt;<br>            jwt.jwtAuthenticationConverter(<br>                keycloakJwtTokenConverter // use custom converter<br>            )<br>        })<br>}</pre><p>add dependency to the project:</p><pre>implementation(&quot;org.springframework.boot:spring-boot-starter&quot;)<br>implementation(&quot;org.springframework.boot:spring-boot-starter-web&quot;)<br>implementation(&quot;org.springframework.boot:spring-boot-starter-security&quot;)<br>implementation(&quot;org.springframework.boot:spring-boot-starter-oauth2-resource-server&quot;)<br>testImplementation(&quot;org.springframework.boot:spring-boot-starter-test&quot;)</pre><h4>KeyCloak converter</h4><p>KeyCloak stores the role information in this structure. We need a KeyCloak converter to extract the roles information from JWTheader:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/929/1*BtV4f-XneQty_HG22cO-RA.png" /></figure><p><a href="https://github.com/softarts/resource-server/blob/main/src/main/kotlin/com/tower/resourceserver/config/SecurityConfig.kt">code</a></p><pre>override fun convert(@NonNull jwt: Jwt): JwtAuthenticationToken {<br>    val accesses = Optional.of&lt;Jwt&gt;(jwt)<br>        .map { token: Jwt -&gt; token.getClaimAsMap(KEYCLOAK_RESOURCE_ACCESS) } // return Map&lt;String, Any&gt;<br>        .map { claimMap: Map&lt;String, Any&gt; -&gt; claimMap[RESOURCE_ID] as Map&lt;String?, Any?&gt;? } // return Map&lt;String?, Any?&gt;?<br>        .map { resourceData: Map&lt;String?, Any?&gt;? -&gt; resourceData!![KEYCLOAK_ROLES] as Collection&lt;String?&gt;? } // return  Collection&lt;String?&gt;?<br>        // it is arrayListOf(&quot;manage-account&quot;,&quot;manage-account-links&quot;,&quot;view-profile&quot;)<br>        .stream().flatMap { x-&gt;x?.stream() } // flatMap the child:ArrayList&lt;String&gt;<br>        .map { role -&gt;<br>            SimpleGrantedAuthority(<br>                KEYCLOAK_ROLE_PREFIX + role<br>            )<br>        }<br><br>    // extract realm_access as well<br>    val realm = Optional.of&lt;Jwt&gt;(jwt)<br>        .map&lt;Map&lt;String?, Any?&gt;&gt; { token: Jwt -&gt; token.getClaimAsMap( KEYCLOAK_REALM_ACCESS) as Map&lt;String?, Any?&gt;?}<br>        .map&lt;Collection&lt;String?&gt;?&gt; { resourceData: Map&lt;String?, Any?&gt;? -&gt; resourceData!![KEYCLOAK_ROLES] as Collection&lt;String?&gt;? } // arrayListOf(&quot;manage-account&quot;,&quot;manage-account-links&quot;,&quot;view-profile&quot;)<br>        .stream().flatMap { x-&gt;x.stream() }<br>        .map { role -&gt;<br>            SimpleGrantedAuthority(<br>                KEYCLOAK_ROLE_PREFIX + role<br>            )<br>        }<br><br>    // concat the 3 streams<br>    val authorities = Stream<br>        .concat(<br>            Stream.concat(jwtGrantedAuthoritiesConverter.convert(jwt).stream(), accesses),<br>            realm<br>        )<br>        .collect(Collectors.toSet())<br><br>    val principalClaimName: String = jwt.getClaimAsString(PRINCIPAL_ATTR) ?:jwt.getClaimAsString(JwtClaimNames.SUB)<br><br>    return JwtAuthenticationToken(jwt, authorities, principalClaimName)<br>}</pre><p>At the end we should get these, “ROLE_user” is the role we assigned for the client/user in the KeyCloak console. ROLE_ is the prefix and user is the role assigned in the KeyCloak console.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/820/1*36fLsJXP-WmGGnTPQ-dN-g.png" /></figure><p>and this is a JWT claims example</p><pre>// an example<br>&quot;sub&quot; -&gt; &quot;dcbfb403-75cf-4388-8c6a-97d642964239&quot;<br>&quot;resource_access&quot; -&gt; {LinkedTreeMap@8450}  size = 1<br>&quot;email_verified&quot; -&gt; {Boolean@8452} false<br>&quot;iss&quot; -&gt; &quot;http://localhost:8080/auth/realms/SpringBootKeycloak&quot;<br>&quot;typ&quot; -&gt; &quot;Bearer&quot;<br>&quot;preferred_username&quot; -&gt; &quot;user1&quot;<br>&quot;sid&quot; -&gt; &quot;ff8bfcdd-1d91-4773-9c84-9b0a12dcd5a2&quot;<br>&quot;aud&quot; -&gt; {ArrayList@8462}  size = 1<br>&quot;acr&quot; -&gt; &quot;1&quot;<br>&quot;realm_access&quot; -&gt; {LinkedTreeMap@8466}  size = 1<br>&quot;azp&quot; -&gt; &quot;login-app&quot;<br>&quot;scope&quot; -&gt; &quot;email profile&quot;<br>&quot;exp&quot; -&gt; {Instant@8426} &quot;2024-01-28T15:04:17Z&quot;<br>&quot;session_state&quot; -&gt; &quot;ff8bfcdd-1d91-4773-9c84-9b0a12dcd5a2&quot;<br>&quot;iat&quot; -&gt; {Instant@8425} &quot;2024-01-28T14:59:17Z&quot;<br>&quot;jti&quot; -&gt; &quot;1d73943b-cc38-4a8d-989f-f120caa59ce0&quot;<br></pre><p>and in the Security Configuration:</p><pre>http.oauth2ResourceServer {<br>    oauth2: OAuth2ResourceServerConfigurer&lt;HttpSecurity?&gt; -&gt;<br>    oauth2.jwt(<br>        Customizer { jwt -&gt;<br>            jwt.jwtAuthenticationConverter(<br>                keycloakJwtTokenConverter // use custom converter<br>            )<br>        })<br>}</pre><p>Configured Authorization server url:</p><pre>spring:<br>  security:<br>    oauth2:<br>      resourceserver:<br>        jwt:<br>          issuer-uri: http://localhost:8080/auth/realms/SpringBootKeycloak</pre><p>If we need to start it independently, then we can supply the <em>jwk-set-uri</em> property instead to point to the authorization server’s endpoint exposing public keys:</p><pre>jwk-set-uri: http://localhost:8080/auth/realms/SpringBootKeycloak/protocol/openid-connect/certs</pre><h3>Testing</h3><ul><li>get access token from the KeyCloak server:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*thN-GAOT5x0svWRMCwg1lQ.png" /></figure><p>or</p><pre>curl --request POST --url http://localhost:8080/auth/realms/SpringBootKeycloak/protocol/openid-connect/token?= --header &quot;Content-Type: application/x-www-form-urlencoded&quot; --data client_id=login-app  --data username=user1  --data password=pass1  --data grant_type=password<br># return token =&gt;<br>{<br> &quot;access_token&quot;: &quot;eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJEQzA3ZTZGSVF5NzVqZFdXOGdNNmM5M1BDNjk5OEpNZVJwS0dfaE1JZU1vIn0.eyJleHAiOjE3MDY5NzAyODAsImlhdCI6MTcwNjk2OTk4MCwianRpIjoiZTlkOGQ2M2QtYTljMy00NTBmLThiMGYtMTI0YmYxMWI3YWYxIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL1NwcmluZ0Jvb3RLZXljbG9hayIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJkY2JmYjQwMy03NWNmLTQzODgtOGM2YS05N2Q2NDI5NjQyMzkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJsb2dpbi1hcHAiLCJzZXNzaW9uX3N0YXRlIjoiM2RmYjEzNDEtNzJlMS00ZTkzLWIyM2MtYWRkYWNjZDAwM2FlIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtc3ByaW5nYm9vdGtleWNsb2FrIiwidW1hX2F1dGhvcml6YXRpb24iLCJ1c2VyIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiM2RmYjEzNDEtNzJlMS00ZTkzLWIyM2MtYWRkYWNjZDAwM2FlIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyMSJ9.OT3do4T0vJo70oGK42Cw8TLej6y_wlCV2VsdlZlKsIZTw0ql5WGigq0YktMP80pUBc14dXbsd10g7PPGUoVqLDTZk-lCaskGsETjUwihyHU1pUbQE9-OARkUfeyiG__JDXOwe-KepyHL9ZKZ_8UL9VYVvm63PtWQfj3_CTW3jfgpynMSa3mnB8QBhDv23fW-VLvXpK7Ab1EcPaYW75J2pFtYb7W7dx73SCLKzEr7cxPJyEpb9D1iz0XuKW2F8l-0LwrIWyGvEaf81bQ9dg-RC_CmhjcGvDHDcP1JFG5G9ad-cZDlgBgDTM2wM8Pw8uR7ZwPnn5fJ9bFq3Iz9zcd_sQ&quot;,<br> &quot;expires_in&quot;: 299,<br> &quot;refresh_expires_in&quot;: 1799,<br> &quot;refresh_token&quot;: &quot;eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzMzM0YTg3YS0xZTRiLTQxOTQtYTVhZi00NjRmMTNhZDBhOGUifQ.eyJleHAiOjE3MDY5NzE3ODAsImlhdCI6MTcwNjk2OTk4MCwianRpIjoiMzhiZGJjZTgtMTEzOC00MDVlLWEyMzUtOGY0YTM0NmZiNGNmIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL1NwcmluZ0Jvb3RLZXljbG9hayIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9TcHJpbmdCb290S2V5Y2xvYWsiLCJzdWIiOiJkY2JmYjQwMy03NWNmLTQzODgtOGM2YS05N2Q2NDI5NjQyMzkiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoibG9naW4tYXBwIiwic2Vzc2lvbl9zdGF0ZSI6IjNkZmIxMzQxLTcyZTEtNGU5My1iMjNjLWFkZGFjY2QwMDNhZSIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6IjNkZmIxMzQxLTcyZTEtNGU5My1iMjNjLWFkZGFjY2QwMDNhZSJ9.wkYEhoi9bSlTT6HQ4R8eIOoj8L2BJc1f4oKHWAXkt8E&quot;,<br> &quot;token_type&quot;: &quot;Bearer&quot;,<br> &quot;not-before-policy&quot;: 0,<br> &quot;session_state&quot;: &quot;3dfb1341-72e1-4e93-b23c-addaccd003ae&quot;,<br> &quot;scope&quot;: &quot;email profile&quot;<br>}</pre><ul><li>access the public endpoint without any token</li></ul><pre>curl http://localhost:8081/public/api1<br># =&gt;<br>public api1</pre><ul><li>accesse private endpoint with token</li></ul><pre># put your actual token return from keycloak<br>curl --request GET --url http://localhost:8081/private/api1 --header &quot;Authorization: Bearer you_token_from_keycloak&quot;<br># =&gt;<br>return private api1</pre><ul><li>accesse the private endpoint without token</li></ul><pre>curl -i http://localhost:8081/private/api1<br># =&gt; return<br>HTTP/1.1 401<br>Set-Cookie: JSESSIONID=DF74D9FAD79621451E27977506C0F0F7; Path=/; HttpOnly<br>WWW-Authenticate: Bearer<br>X-Content-Type-Options: nosniff<br>X-XSS-Protection: 0<br>Cache-Control: no-cache, no-store, max-age=0, must-revalidate<br>Pragma: no-cache<br>Expires: 0<br>X-Frame-Options: DENY<br>Content-Length: 0<br>Date: Sat, 03 Feb 2024 14:33:25 GMT</pre><p>or with insufficient role permission (/private/admin need “admin” role)</p><pre>&gt;curl -i --request GET --url http://localhost:8081/private/admin --header &quot;Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJEQzA3ZTZGSVF5NzVqZFdXOGdNNmM5M1BDNjk5OEpNZVJwS0dfaE1JZU1vIn0.eyJleHAiOjE3MDY5NzQ2MjEsImlhdCI6MTcwNjk3NDMyMSwianRpIjoiMzliYzIwZDQtOWM1Yi00YjVjLWFkYzctY2RmNTc5MmUxMDRmIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL1NwcmluZ0Jvb3RLZXljbG9hayIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJkY2JmYjQwMy03NWNmLTQzODgtOGM2YS05N2Q2NDI5NjQyMzkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJsb2dpbi1hcHAiLCJzZXNzaW9uX3N0YXRlIjoiNDZkNWRlNmMtMjZiYy00MjI4LWEyZDQtM2MxM2U3NzFiNGIyIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtc3ByaW5nYm9vdGtleWNsb2FrIiwidW1hX2F1dGhvcml6YXRpb24iLCJ1c2VyIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiNDZkNWRlNmMtMjZiYy00MjI4LWEyZDQtM2MxM2U3NzFiNGIyIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyMSJ9.IrZaC_IqmXxT9GV6mAMw0YXInN6Dge9iRaF7NUiErNt5zKk_7tPGVr5s4Z1RdctSk-Xv7S9XW9JmSpSnttmlxGuE_G3bhED1rCEpuVp5674nOghoq-80gbVmQC4rl9Uk0JIgk6JDFB03uC4Lg6tDuAUYfnJ3QbuPUsVc97Dml-5dPKoVfP7Q-D6NvzvO87EtBm5Io6evZkDE30bccmpBRLUtb4dA5DexCV4xEtFxDwSJmoCBwb8Bs_0CuxT7ZFw1C7QZJvFEbppNMtAf7YiUwVPW_pMNAKxCJP47GZrSvhzz41PCE7tYrUEoXiPKRPp1WRN55de5p8VJE2l8_TMGCA&quot;<br># return =&gt;<br>HTTP/1.1 403<br>WWW-Authenticate: Bearer error=&quot;insufficient_scope&quot;, error_description=&quot;The request requires higher privileges than provided by the access token.&quot;, error_uri=&quot;https://tools.ietf.org/html/rfc6750#section-3.1&quot;<br>X-Content-Type-Options: nosniff<br>X-XSS-Protection: 0<br>Cache-Control: no-cache, no-store, max-age=0, must-revalidate<br>Pragma: no-cache<br>Expires: 0<br>X-Frame-Options: DENY<br>Content-Length: 0<br>Date: Sat, 03 Feb 2024 15:32:30 GMT</pre><h3>Reference</h3><ul><li><a href="https://dzone.com/articles/spring-boot-3-keycloak">https://dzone.com/articles/spring-boot-3-keycloak</a> (src code: <a href="https://github.com/Pask423/keycloak-springboot/tree/master/base-integration-spring-boot-3">https://github.com/Pask423/keycloak-springboot/tree/master/base-integration-spring-boot-3</a>)</li><li><a href="https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html">https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html</a></li><li><a href="https://www.baeldung.com/spring-boot-keycloak">https://www.baeldung.com/spring-boot-keycloak</a></li><li><a href="https://www.keycloak.org/2023/03/adapter-deprecation-update">https://www.keycloak.org/2023/03/adapter-deprecation-update</a></li><li><a href="https://www.keycloak.org/2022/02/adapter-deprecation.html">https://www.keycloak.org/2022/02/adapter-deprecation.html</a></li><li><a href="https://www.baeldung.com/spring-security-method-security">https://www.baeldung.com/spring-security-method-security</a></li><li><a href="https://github.com/softarts/resource-server">https://github.com/softarts/resource-server</a> (source code)</li></ul><p><strong><em>Happy Coding!</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c447bbca363c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Understand sequence diagram and trace distributed applications with Open Telemetry/PlantUml SDK]]></title>
            <link>https://medium.com/@wirelesser/understand-sequence-diagram-and-trace-distributed-applications-with-open-telemetry-plantuml-sdk-ace13688de74?source=rss-8689b3073a6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/ace13688de74</guid>
            <category><![CDATA[docker-client]]></category>
            <category><![CDATA[sequence-diagrams]]></category>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[plantuml]]></category>
            <category><![CDATA[distributed-tracing]]></category>
            <dc:creator><![CDATA[Rui Zhou]]></dc:creator>
            <pubDate>Fri, 29 Dec 2023 14:26:53 GMT</pubDate>
            <atom:updated>2024-01-03T14:49:27.362Z</atom:updated>
            <content:encoded><![CDATA[<p>In this article, I built an application(<strong>sequencetracing</strong>) to understand the PlantUml script and trace the real HTTP calls across multiple spring-boot micro-services.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SSCMtYT1uMvy1fYMEaEVKw.png" /><figcaption>image created by sequencetracing</figcaption></figure><p>There are already lots of excellent commercial APM/Observability Tools can help us to trace the HTTP calls chain among distributed applications. But none of them can understand PlantUml/sequence diagram and draw a nice sequence diagram yet.</p><h3>Demo</h3><p>The source code is <a href="https://github.com/softarts/sequencetracing">here</a>.</p><p>I prepare a sequence diagram written in <a href="https://plantuml.com/sequence-diagram">plantuml</a></p><pre>@startuml<br>title Gateway flow<br>participant gateway<br>participant appserver<br>participant dataserver<br>gateway -&gt; appserver: /test1<br>note right<br>url: v1/token/generate<br>header: api-key<br>param: card-no<br>end note<br><br>appserver -&gt; dataserver: /test1<br>dataserver -&gt; appserver: /test1<br>appserver -&gt; gateway: /test1<br>@enduml</pre><p>the expected sequence:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/336/1*2N2D5WJU7Yf6PLUr7cDUwg.png" /></figure><p>Then I started gateway, appserver, dataserver the 3 applications via docker-compose, and Initial a call /test1from <a href="https://insomnia.rest/">insomnia</a> with x-bdd-corrid = 1234abcd in header.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6zilNSQnOsP5qW3YMwa_nQ.png" /></figure><p>The sequencetracing will monitor the docker logs and draw the actual call sequence in the image.</p><h4>Happy flow</h4><p>in appserver</p><pre>@GetMapping(&quot;/test1&quot;)<br>fun test1(): String  {<br>    val url = &quot;http://data:8083/test1&quot;<br>    val restTemplate = RestTemplate()<br>    val responseEntity: ResponseEntity&lt;String&gt; = restTemplate.getForEntity(url, String::class.java)<br>    val resp: String? = responseEntity.body<br>    println(&quot;test1 resp: $resp&quot;)<br>    return &quot;app test1$resp&quot;<br>}</pre><p>everything is hit and returned successfully =&gt;</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/577/1*ZPcImAHZ5GvPJdSWgu7L_w.png" /></figure><h4>Broken flow</h4><p>put some bad stuff in the appserver controller, hence appserver won’t send HTTP call to the dataserver</p><pre>@GetMapping(&quot;/test2&quot;)<br>fun test2(): String  {<br>    throw RuntimeException(&quot;app test2&quot;)<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/578/1*2G2FZpGPPybVkzL_qAvBdA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/649/1*uE7TyMBGptRb1zU2hyg7mw.png" /></figure><p>The sequencetracing application detects the HTTP call appserver-&gt;dataserver is missing, so it draws it in RED color; While gateway-&gt;appserver call is seen but the HTTP status code is 500 (internal server error because appserver throws an exception), hence it draws it as YELLOW and with an X symbol.</p><h3>Step by step</h3><ul><li>prepare, download the source code, install the docker environment, build the docker image for gateway, appserver, and dataserver</li></ul><pre>git clone https://github.com/softarts/sequencetracing.git<br>cd gateway<br>gradlew build # or gradlew.bat build <br>docker build -t gateway .<br><br># Do the same for appserver and dataserver</pre><ul><li>start docker container</li></ul><pre>cd sequencetracing<br>docker-compose up</pre><ul><li>Send HTTP request with postman, insomnia or curl</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O-QBS3r__U5YxPrDg87cEg.png" /></figure><p>curl =&gt;</p><pre>curl -H &quot;x-bdd-corrid:qwert&quot; http://localhost:8080/test2<br># &#39;qwert&#39; is the corrid<br># =&gt;<br>{&quot;timestamp&quot;:&quot;2023-12-29T13:15:09.410+00:00&quot;,&quot;status&quot;:500,&quot;error&quot;:&quot;Internal Server Error&quot;,&quot;path&quot;:&quot;/test2&quot;}</pre><ul><li>Run the sequencetracing application(I put the code in unit test so run through gradle test, and the parameters are passed through system property)</li></ul><pre>#corrHeader=x_bdd_corrid (the header name we will put in the HTTP request)<br>#corrid=qwert (the header value we will put in the HTTP request)<br>#uml=src/test/resources/seqtrace/gateway-test1.puml (input uml file) <br>#output=src/test/resources/seqtrace/gateway-test1.png (output image)<br><br>cd sequencetracing/sequencetracing<br><br># use the corrid set in the previous step(curl/postman/insomnia)<br># /test1<br>gradlew.bat test --tests SeqTraceTest -DcorrHeader=x_bdd_corrid -Doutput=src/test/resources/seqtrace/gateway-test1.png -Dcorrid=qwert -Duml=src/test/resources/seqtrace/gateway-test1.puml --no-daemon --rerun<br><br># /test2<br># -info will print all the system output<br>gradlew.bat test --tests SeqTraceTest -DcorrHeader=x_bdd_corrid -Doutput=src/test/resources/seqtrace/gateway-test2.png -Dcorrid=qwert -Duml=src/test/resources/seqtrace/gateway-test2.puml --no-daemon -info</pre><p>=&gt; debug output</p><pre>net.host.name                            --&gt; appserver<br>user_agent.original                      --&gt; Java/17.0.7<br>http.target                              --&gt; /test2<br>net.sock.peer.addr                       --&gt; 172.21.0.4<br>thread.name                              --&gt; http-nio-8082-exec-1<br>http.status_code                         --&gt; 500<br>net.sock.host.addr                       --&gt; 172.21.0.2<br>http.route                               --&gt; /test2<br>net.host.port                            --&gt; 8082<br>net.protocol.name                        --&gt; http<br>net.sock.peer.port                       --&gt; 47786<br>http.method                              --&gt; GET<br>http.scheme                              --&gt; http<br>thread.id                                --&gt; 25<br>net.protocol.version                     --&gt; 1.1<br>net.sock.host.port                       --&gt; 8082<br>container appserver is server<br><br><br></pre><p>correlation output =&gt;</p><pre>CONTAINER                                          TRACE                                              SPAN                                               RAW<br>/gateway                                           e5ec972f3eb5d53975877a6d78d6d9b2                   0e4fd362145f56c2                                   [otel.javaagent 2023-12-29 11:06:55:492 +0000] [ht<br>/gateway                                           e5ec972f3eb5d53975877a6d78d6d9b2                   71e6163c32e369fd                                   [otel.javaagent 2023-12-29 11:06:55:506 +0000] [ht<br>/gateway                                           e5ec972f3eb5d53975877a6d78d6d9b2                   5054e2e10738ab48                                   [otel.javaagent 2023-12-29 11:06:55:674 +0000] [ht<br>/gateway                                           e5ec972f3eb5d53975877a6d78d6d9b2                   59bad2f1c117827e                                   [otel.javaagent 2023-12-29 11:06:55:676 +0000] [ht<br>/appserver                                         e5ec972f3eb5d53975877a6d78d6d9b2                   fa8be9d35d35db86                                   [otel.javaagent 2023-12-29 11:06:55:352 +0000] [ht<br>/appserver                                         e5ec972f3eb5d53975877a6d78d6d9b2                   f63c534155e0b969                                   [otel.javaagent 2023-12-29 11:06:55:486 +0000] [ht<br>/appserver                                         e5ec972f3eb5d53975877a6d78d6d9b2                   58bd47e93aec25f7                                   [otel.javaagent 2023-12-29 11:06:55:488 +0000] [ht<br><br>URI                                                SRC                                                DST                                                REQ        RESP       STATUS_CODE<br>http://localhost:8080/test2                                                                           gateway                                            Y          N          500<br>http://appserver:8082/test2                        gateway                                            appserver                                          Y          Y          500<br><br>SRC                                                DST                                                LABEL                                              CONNECTION      HTTP_SUCC<br>gateway                                            appserver                                          /test2                                             &lt;=&gt;             false<br>appserver                                          dataserver                                         /test2                                             -/-             false<br>dataserver                                         appserver                                          /test2                                             -/-             false<br>appserver                                          gateway                                            /test2                                             &lt;=&gt;             false<br># &lt;=&gt; means the call is observed<br># -/- means call is NOT seen.</pre><p>the output image(generated in src/test/resources/seqtrace/gateway-test2.png) =&gt;</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ijShty8XgddhT70Hhurytw.png" /></figure><h3>How does it work</h3><h4>Open Telemetry</h4><p>Most of the APM tools, such as Datadog, sumologic, and AppDynamic, are running on top of open-telemetry.</p><p>the open-telemetry agent runs within the same JVM space as the application, intercepts all the API calls from the application, and either produces to logs or send back to the Collector.</p><figure><img alt="https://www.datadoghq.com/knowledge-center/opentelemetry/" src="https://cdn-images-1.medium.com/max/1024/1*lH2Knlg2nIlkc86SQwhjdQ.png" /></figure><p>Use JAVA_TOOL_OPTIONS to load Open Telemetry agent in each JVM application. see their Dockerfiles, also need to configure it to capture the HTTP header x-bdd-corrid</p><pre>FROM azul/zulu-openjdk:17.0.7-17.42.19-jre<br><br>COPY build/libs/*SNAPSHOT.jar /app/app.jar<br><br>ENV JAVA_TOOL_OPTIONS=&quot;-javaagent:/app/lib/opentelemetry-javaagent.jar&quot;<br>ENV OTEL_TRACES_EXPORTER=logging<br>ENV OTEL_METRICS_EXPORTER=logging<br>ENV OTEL_LOGS_EXPORTER=logging<br>ENV OTEL_INSTRUMENTATION_HTTP_SERVER_CAPTURE_REQUEST_HEADERS=x-bdd-corrid<br><br>EXPOSE 8082<br><br>ENTRYPOINT [&quot;java&quot;,  \<br>            &quot;-server&quot;, \<br>            &quot;-cp&quot;, &quot;/app:/app/lib/*&quot;, \<br>            &quot;-XX:MaxRAMPercentage=60.0&quot;, \<br>            &quot;-XX:+UnlockDiagnosticVMOptions&quot;, \<br>            &quot;-XX:NativeMemoryTracking=summary&quot;, \<br>            &quot;-XX:+PrintNMTStatistics&quot;, \<br>            &quot;-jar&quot;, &quot;/app/app.jar&quot;]</pre><p>and I prepared a <a href="https://github.com/softarts/sequencetracing/blob/main/docker-compose.yaml">docker-compose.yaml</a> to start all three applications, opentelemetry-javaagent will be mapped to each container.</p><pre><br>version: &quot;3&quot;<br><br>services:<br>  gateway:<br>    image: gateway:latest<br>    container_name: gateway<br>    hostname: gateway<br>    restart: always<br>    ports:<br>      - 8080:8080<br>    volumes:<br>      - ./lib:/app/lib<br>  appserver:<br>    image: appserver:latest<br>    container_name: appserver<br>    hostname: appserver<br>    restart: always<br>    ports:<br>      - 8082:8082<br>    volumes:<br>      - ./lib:/app/lib      <br>  dataserver:<br>    image: dataserver:latest<br>    container_name: dataserver<br>    hostname: dataserver<br>    restart: always<br>    ports:<br>      - 8083:8083<br>    volumes:<br>      - ./lib:/app/lib</pre><p>I uploaded an opentelemetry-javaagentto <a href="https://github.com/softarts/sequencetracing/blob/main/lib/opentelemetry-javaagent.jar">here</a>, you can also download the latest version from its GitHub release <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases">page</a></p><h4>Search docker container log with regex</h4><p>The next step is to monitor the docker container logs and parse them. the log fetching part I wrote mainly refers to <a href="https://github.com/testcontainers/testcontainers-java/tree/main/core/src/main/java/org/testcontainers/containers/output">testcontainers</a> , then I wrote a quick and dirty part myself to parse these logs and correlate the src/dst container and HTTP call.</p><pre>docker-compose up<br>...xxx<br>gateway    | Picked up JAVA_TOOL_OPTIONS: -javaagent:/app/opentelemetry-javaagent.jar<br>gateway    | OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended<br>...<br>gateway    | [otel.javaagent 2023-12-25 14:47:54:467 +0000] [http-nio-8080-exec-1] INFO io.opentelemetry.exporter.logging.LoggingSpanExporter - &#39;GET /test1&#39; : 42f38e33ab2f2ebb4f2d8715de482e89 ec52189d7f097cab SERVER [tracer: io.opentelemetry.tomcat-10.0:1.32.0-alpha] AttributesMap{data={http.route=/test1, net.sock.host.addr=172.19.0.2, net.protocol.name=http, net.protocol.version=1.1, http.scheme=http, http.method=GET, net.host.port=8080, http.request.header.x_bdd_corrid=[1234abcd], net.host.name=localhost, user_agent.original=insomnia/2023.5.8, http.response_content_length=29, http.target=/test1, http.status_code=200, net.sock.peer.addr=172.19.0.1, net.sock.host.port=8080, thread.id=25, net.sock.peer.port=43320, thread.name=http-nio-8080-exec-1}, capacity=128, totalAddedValues=18}</pre><p>The last line is what the open telemetry has intercepted when there is an HTTP request. The sequencetracing application just needs to parse these logs and correlate (by trace, span, corr Id) them later on, which is similar to what the APM tools do at the backend.</p><p>I created the sequencetracing project based on <a href="https://github.com/esteinberg/plantuml4idea"><strong>plantuml intellij plugin</strong></a> , to parse and generate the PlantUml sequence diagram.</p><p>See this <a href="https://github.com/softarts/sequencetracing/pull/1">PR</a> for the change I made.</p><h3><strong><em>Happy Coding!</em></strong></h3><p>It is just a Proof-of-concept project and NOT robust yet. It is probably unable to handle the case where that async call is involved, e.g. Kafka events.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ace13688de74" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real-Time Message Ingestion to Big Data Platform]]></title>
            <link>https://medium.com/better-programming/real-time-message-ingestion-to-big-data-platform-a8236dfab7bc?source=rss-8689b3073a6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/a8236dfab7bc</guid>
            <category><![CDATA[hdfs]]></category>
            <category><![CDATA[big-data-pipeline]]></category>
            <category><![CDATA[big-data-platform]]></category>
            <category><![CDATA[kafka-connect]]></category>
            <category><![CDATA[kafka]]></category>
            <dc:creator><![CDATA[Rui Zhou]]></dc:creator>
            <pubDate>Tue, 24 Oct 2023 14:46:41 GMT</pubDate>
            <atom:updated>2023-10-24T14:46:41.762Z</atom:updated>
            <content:encoded><![CDATA[<p>A practice to ingest the data in real-time from Kafka cluster to the Hadoop/HDFS platform</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*obBbhyIgbLe9vZZO" /><figcaption>Photo by <a href="https://unsplash.com/@sortino?utm_source=medium&amp;utm_medium=referral">Joshua Sortino</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>It is quite a common requirement to ingest the data from the microservice cluster to the big data platform for further analytics. Depending on the data platform architecture, it can ingest data to either the object store(s3) or Hadoop/HDFS</p><h3>Data Architecture</h3><h4>Hadoop/HDFS based</h4><figure><img alt="https://adityacodes.medium.com/my-big-data-journey-with-cloudera-72cd21019204" src="https://cdn-images-1.medium.com/max/1024/1*0hi1lf_UdmIiSmwZYM1HPg.png" /><figcaption><a href="https://adityacodes.medium.com/my-big-data-journey-with-cloudera-72cd21019204">https://adityacodes.medium.com/my-big-data-journey-with-cloudera-72cd21019204</a></figcaption></figure><p>This tech stack is a bit outdated, but lots of companies are still using it nowadays.</p><h4>Object Store(S3) based</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vx452wy7ve6Ysohp3melcA.png" /><figcaption><a href="https://learn.microsoft.com/en-us/azure/architecture/example-scenario/dataplate2e/data-platform-end-to-end?tabs=portal">https://learn.microsoft.com/en-us/azure/architecture/example-scenario/dataplate2e/data-platform-end-to-end?tabs=portal</a></figcaption></figure><p>object store/s3 as data lake is more popular when the data platform is deployed on the public cloud. No matter which deployment, there is not much difference in the data-sink(ingestion to data platform) part. Both of them will use Kafka-Connect to ingest data to data platform. I will give an example based on the Hadoop solution.</p><h3>Start the environment</h3><p>I created a docker-compose file run Hadoop and Kafka. refer to</p><p><a href="https://github.com/confluentinc/cp-all-in-one/tree/7.5.0-post/cp-all-in-one-community">cp-all-in-one/cp-all-in-one-community at 7.5.0-post · confluentinc/cp-all-in-one</a></p><p>and <a href="https://github.com/big-data-europe/docker-hadoop">https://github.com/big-data-europe/docker-hadoop</a></p><p>docker-compose.yaml</p><pre>version: &quot;3&quot;<br><br>services:<br>  namenode:<br>    image: bde2020/hadoop-namenode:2.0.0-hadoop3.2.1-java8<br>    container_name: namenode<br>    restart: always<br>    ports:<br>      - 9870:9870<br>      - 9000:9000<br>    volumes:<br>      - hadoop_namenode:/hadoop/dfs/name<br>    environment:<br>      - CLUSTER_NAME=test<br>    env_file:<br>      - ./hadoop.env<br><br>  datanode:<br>    image: bde2020/hadoop-datanode:2.0.0-hadoop3.2.1-java8<br>    container_name: datanode<br>    restart: always<br>    volumes:<br>      - hadoop_datanode:/hadoop/dfs/data<br>    environment:<br>      SERVICE_PRECONDITION: &quot;namenode:9870&quot;<br>    env_file:<br>      - ./hadoop.env<br>  <br>  resourcemanager:<br>    image: bde2020/hadoop-resourcemanager:2.0.0-hadoop3.2.1-java8<br>    container_name: resourcemanager<br>    restart: always<br>    environment:<br>      SERVICE_PRECONDITION: &quot;namenode:9000 namenode:9870 datanode:9864&quot;<br>    env_file:<br>      - ./hadoop.env<br><br>  nodemanager1:<br>    image: bde2020/hadoop-nodemanager:2.0.0-hadoop3.2.1-java8<br>    container_name: nodemanager<br>    restart: always<br>    environment:<br>      SERVICE_PRECONDITION: &quot;namenode:9000 namenode:9870 datanode:9864 resourcemanager:8088&quot;<br>    env_file:<br>      - ./hadoop.env<br>  <br>  historyserver:<br>    image: bde2020/hadoop-historyserver:2.0.0-hadoop3.2.1-java8<br>    container_name: historyserver<br>    restart: always<br>    environment:<br>      SERVICE_PRECONDITION: &quot;namenode:9000 namenode:9870 datanode:9864 resourcemanager:8088&quot;<br>    volumes:<br>      - hadoop_historyserver:/hadoop/yarn/timeline<br>    env_file:<br>      - ./hadoop.env<br>  <br>  zookeeper:<br>    image: confluentinc/cp-zookeeper:7.5.0<br>    hostname: zookeeper<br>    container_name: zookeeper<br>    ports:<br>      - &quot;2181:2181&quot;<br>    environment:<br>      ZOOKEEPER_CLIENT_PORT: 2181<br>      ZOOKEEPER_TICK_TIME: 2000<br><br>  broker:<br>    image: confluentinc/cp-server:7.5.0<br>    hostname: broker<br>    container_name: broker<br>    depends_on:<br>      - zookeeper<br>    ports:<br>      - &quot;9092:9092&quot;<br>      - &quot;9101:9101&quot;<br>    environment:<br>      KAFKA_BROKER_ID: 1<br>      KAFKA_ZOOKEEPER_CONNECT: &#39;zookeeper:2181&#39;<br>      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT<br>      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://broker:29092,PLAINTEXT_HOST://localhost:9092<br>      KAFKA_METRIC_REPORTERS: io.confluent.metrics.reporter.ConfluentMetricsReporter<br>      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1<br>      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0<br>      KAFKA_CONFLUENT_LICENSE_TOPIC_REPLICATION_FACTOR: 1<br>      KAFKA_CONFLUENT_BALANCER_TOPIC_REPLICATION_FACTOR: 1<br>      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1<br>      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1<br>      KAFKA_JMX_PORT: 9101<br>      KAFKA_JMX_HOSTNAME: localhost<br>      KAFKA_CONFLUENT_SCHEMA_REGISTRY_URL: http://schema-registry:8081<br>      CONFLUENT_METRICS_REPORTER_BOOTSTRAP_SERVERS: broker:29092<br>      CONFLUENT_METRICS_REPORTER_TOPIC_REPLICAS: 1<br>      CONFLUENT_METRICS_ENABLE: &#39;true&#39;<br>      CONFLUENT_SUPPORT_CUSTOMER_ID: &#39;anonymous&#39;<br><br>  schema-registry:<br>    image: confluentinc/cp-schema-registry:7.5.0<br>    hostname: schema-registry<br>    container_name: schema-registry<br>    depends_on:<br>      - broker<br>    ports:<br>      - &quot;8081:8081&quot;<br>    environment:<br>      SCHEMA_REGISTRY_HOST_NAME: schema-registry<br>      SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: &#39;broker:29092&#39;<br>      SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:8081  <br><br>  connect:<br>    image: confluentinc/cp-kafka-connect:latest    <br>    hostname: connect<br>    container_name: connect<br>    depends_on:<br>      - zookeeper<br>      - broker<br>      - schema-registry<br>    ports:<br>      - 8083:8083<br>    environment:<br>      CONNECT_BOOTSTRAP_SERVERS: &#39;broker:29092&#39;<br>      CONNECT_REST_ADVERTISED_HOST_NAME: connect<br>      CONNECT_REST_PORT: 8083<br><br>      CONNECT_GROUP_ID: compose-connect-group<br>      CONNECT_CONFIG_STORAGE_TOPIC: docker-connect-configs<br>      CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1<br>      CONNECT_OFFSET_FLUSH_INTERVAL_MS: 10000<br>      CONNECT_OFFSET_STORAGE_TOPIC: docker-connect-offsets<br>      CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1<br>      CONNECT_STATUS_STORAGE_TOPIC: docker-connect-status<br>      CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1<br><br><br>      CONNECT_KEY_CONVERTER: io.confluent.connect.avro.AvroConverter<br>      CONNECT_KEY_CONVERTER_SCHEMA_REGISTRY_URL: http://schema-registry:8081<br>      <br>      CONNECT_VALUE_CONVERTER: io.confluent.connect.avro.AvroConverter<br>      CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL: http://schema-registry:8081<br>      <br>  <br>      CLASSPATH: /usr/share/java/monitoring-interceptors/monitoring-interceptors-7.5.0.jar<br>      CONNECT_PRODUCER_INTERCEPTOR_CLASSES: &quot;io.confluent.monitoring.clients.interceptor.MonitoringProducerInterceptor&quot;<br>      CONNECT_CONSUMER_INTERCEPTOR_CLASSES: &quot;io.confluent.monitoring.clients.interceptor.MonitoringConsumerInterceptor&quot;<br><br>      CONNECT_LOG4J_ROOT_LOGLEVEL: &quot;INFO&quot;<br>      CONNECT_LOG4J_LOGGERS: &quot;org.apache.kafka.connect.runtime.rest=WARN,org.reflections=ERROR&quot;      <br>      CONNECT_PLUGIN_PATH: &#39;/usr/share/java,/usr/share/confluent-hub-components/,/connectors/&#39;<br>    <br>    command:<br>      - sh<br>      - -exc<br>      - |<br>        confluent-hub install --no-prompt --component-dir /usr/share/confluent-hub-components/ confluentinc/kafka-connect-hdfs:latest<br>        confluent-hub install --no-prompt --component-dir /usr/share/confluent-hub-components/ confluentinc/kafka-connect-datagen:latest<br>        exec /etc/confluent/docker/run<br><br>  control-center:<br>    image: confluentinc/cp-enterprise-control-center:7.5.0<br>    hostname: control-center<br>    container_name: control-center<br>    depends_on:<br>      - broker<br>      - schema-registry<br>      - connect<br>      - ksqldb-server<br>    ports:<br>      - &quot;9021:9021&quot;<br>    environment:<br>      CONTROL_CENTER_BOOTSTRAP_SERVERS: &#39;broker:29092&#39;<br>      CONTROL_CENTER_CONNECT_CONNECT-DEFAULT_CLUSTER: &#39;connect:8083&#39;<br>      CONTROL_CENTER_KSQL_KSQLDB1_URL: &quot;http://ksqldb-server:8088&quot;<br>      CONTROL_CENTER_KSQL_KSQLDB1_ADVERTISED_URL: &quot;http://localhost:8088&quot;<br>      CONTROL_CENTER_SCHEMA_REGISTRY_URL: &quot;http://schema-registry:8081&quot;<br>      CONTROL_CENTER_REPLICATION_FACTOR: 1<br>      CONTROL_CENTER_INTERNAL_TOPICS_PARTITIONS: 1<br>      CONTROL_CENTER_MONITORING_INTERCEPTOR_TOPIC_PARTITIONS: 1<br>      CONFLUENT_METRICS_TOPIC_REPLICATION: 1      <br>      PORT: 9021<br>      CONTROL_CENTER_CONNECT_HEALTHCHECK_ENDPOINT: &#39;/connectors&#39;<br><br>  ksqldb-server:<br>    image: confluentinc/cp-ksqldb-server:7.5.0<br>    hostname: ksqldb-server<br>    container_name: ksqldb-server<br>    depends_on:<br>      - broker<br>      - connect<br>    ports:<br>      - &quot;8088:8088&quot;<br>    environment:<br>      KSQL_CONFIG_DIR: &quot;/etc/ksql&quot;<br>      KSQL_BOOTSTRAP_SERVERS: &quot;broker:29092&quot;<br>      KSQL_HOST_NAME: ksqldb-server<br>      KSQL_LISTENERS: &quot;http://0.0.0.0:8088&quot;<br>      KSQL_CACHE_MAX_BYTES_BUFFERING: 0<br>      KSQL_KSQL_SCHEMA_REGISTRY_URL: &quot;http://schema-registry:8081&quot;<br>      KSQL_PRODUCER_INTERCEPTOR_CLASSES: &quot;io.confluent.monitoring.clients.interceptor.MonitoringProducerInterceptor&quot;<br>      KSQL_CONSUMER_INTERCEPTOR_CLASSES: &quot;io.confluent.monitoring.clients.interceptor.MonitoringConsumerInterceptor&quot;<br>      KSQL_KSQL_CONNECT_URL: &quot;http://connect:8083&quot;<br>      KSQL_KSQL_LOGGING_PROCESSING_TOPIC_REPLICATION_FACTOR: 1<br>      KSQL_KSQL_LOGGING_PROCESSING_TOPIC_AUTO_CREATE: &#39;true&#39;<br>      KSQL_KSQL_LOGGING_PROCESSING_STREAM_AUTO_CREATE: &#39;true&#39;<br><br>  ksqldb-cli:<br>    image: confluentinc/cp-ksqldb-cli:7.5.0<br>    container_name: ksqldb-cli<br>    depends_on:<br>      - broker<br>      - connect<br>      - ksqldb-server<br>    entrypoint: /bin/sh<br>    tty: true<br><br>  ksql-datagen:<br>    image: confluentinc/ksqldb-examples:7.5.0<br>    hostname: ksql-datagen<br>    container_name: ksql-datagen<br>    depends_on:<br>      - ksqldb-server<br>      - broker<br>      - schema-registry<br>      - connect<br>    command: &quot;bash -c &#39;echo Waiting for Kafka to be ready... &amp;&amp; \<br>                       cub kafka-ready -b broker:29092 1 40 &amp;&amp; \<br>                       echo Waiting for Confluent Schema Registry to be ready... &amp;&amp; \<br>                       cub sr-ready schema-registry 8081 40 &amp;&amp; \<br>                       echo Waiting a few seconds for topic creation to finish... &amp;&amp; \<br>                       sleep 11 &amp;&amp; \<br>                       tail -f /dev/null&#39;&quot;<br>    environment:<br>      KSQL_CONFIG_DIR: &quot;/etc/ksql&quot;<br>      STREAMS_BOOTSTRAP_SERVERS: broker:29092<br>      STREAMS_SCHEMA_REGISTRY_HOST: schema-registry<br>      STREAMS_SCHEMA_REGISTRY_PORT: 8081<br><br>  rest-proxy:<br>    image: confluentinc/cp-kafka-rest:7.5.0<br>    depends_on:<br>      - broker<br>      - schema-registry<br>    ports:<br>      - 8082:8082<br>    hostname: rest-proxy<br>    container_name: rest-proxy<br>    environment:<br>      KAFKA_REST_HOST_NAME: rest-proxy<br>      KAFKA_REST_BOOTSTRAP_SERVERS: &#39;broker:29092&#39;<br>      KAFKA_REST_LISTENERS: &quot;http://0.0.0.0:8082&quot;<br>      KAFKA_REST_SCHEMA_REGISTRY_URL: &#39;http://schema-registry:8081&#39;<br>volumes:<br>  hadoop_namenode:<br>  hadoop_datanode:<br>  hadoop_historyserver:<br></pre><p>hadoop.env</p><pre>CORE_CONF_fs_defaultFS=hdfs://namenode:9000<br>CORE_CONF_hadoop_http_staticuser_user=root<br>CORE_CONF_hadoop_proxyuser_hue_hosts=*<br>CORE_CONF_hadoop_proxyuser_hue_groups=*<br>CORE_CONF_io_compression_codecs=org.apache.hadoop.io.compress.SnappyCodec<br><br>HDFS_CONF_dfs_webhdfs_enabled=true<br>HDFS_CONF_dfs_permissions_enabled=false<br>HDFS_CONF_dfs_namenode_datanode_registration_ip___hostname___check=false<br><br>YARN_CONF_yarn_log___aggregation___enable=true<br>YARN_CONF_yarn_log_server_url=http://historyserver:8188/applicationhistory/logs/<br>YARN_CONF_yarn_resourcemanager_recovery_enabled=true<br>YARN_CONF_yarn_resourcemanager_store_class=org.apache.hadoop.yarn.server.resourcemanager.recovery.FileSystemRMStateStore<br>YARN_CONF_yarn_resourcemanager_scheduler_class=org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler<br>YARN_CONF_yarn_scheduler_capacity_root_default_maximum___allocation___mb=8192<br>YARN_CONF_yarn_scheduler_capacity_root_default_maximum___allocation___vcores=4<br>YARN_CONF_yarn_resourcemanager_fs_state___store_uri=/rmstate<br>YARN_CONF_yarn_resourcemanager_system___metrics___publisher_enabled=true<br>YARN_CONF_yarn_resourcemanager_hostname=resourcemanager<br>YARN_CONF_yarn_resourcemanager_address=resourcemanager:8032<br>YARN_CONF_yarn_resourcemanager_scheduler_address=resourcemanager:8030<br>YARN_CONF_yarn_resourcemanager_resource__tracker_address=resourcemanager:8031<br>YARN_CONF_yarn_timeline___service_enabled=true<br>YARN_CONF_yarn_timeline___service_generic___application___history_enabled=true<br>YARN_CONF_yarn_timeline___service_hostname=historyserver<br>YARN_CONF_mapreduce_map_output_compress=true<br>YARN_CONF_mapred_map_output_compress_codec=org.apache.hadoop.io.compress.SnappyCodec<br>YARN_CONF_yarn_nodemanager_resource_memory___mb=16384<br>YARN_CONF_yarn_nodemanager_resource_cpu___vcores=8<br>YARN_CONF_yarn_nodemanager_disk___health___checker_max___disk___utilization___per___disk___percentage=98.5<br>YARN_CONF_yarn_nodemanager_remote___app___log___dir=/app-logs<br>YARN_CONF_yarn_nodemanager_aux___services=mapreduce_shuffle<br><br>MAPRED_CONF_mapreduce_framework_name=yarn<br>MAPRED_CONF_mapred_child_java_opts=-Xmx4096m<br>MAPRED_CONF_mapreduce_map_memory_mb=4096<br>MAPRED_CONF_mapreduce_reduce_memory_mb=8192<br>MAPRED_CONF_mapreduce_map_java_opts=-Xmx3072m<br>MAPRED_CONF_mapreduce_reduce_java_opts=-Xmx6144m<br>MAPRED_CONF_yarn_app_mapreduce_am_env=HADOOP_MAPRED_HOME=/opt/hadoop-3.2.1/<br>MAPRED_CONF_mapreduce_map_env=HADOOP_MAPRED_HOME=/opt/hadoop-3.2.1/<br>MAPRED_CONF_mapreduce_reduce_env=HADOOP_MAPRED_HOME=/opt/hadoop-3.2.1/</pre><h3>Configuration steps</h3><h4>Create a Kafka topic</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4XnU7Qy4Hi2M16j0AHZcOA.png" /></figure><p>open localhost:9021 , Click Add topic</p><p>create topic data-sink-hdfs</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*txGBdZWf4g-2cDOT3MB_6Q.png" /></figure><h4>Add a connector</h4><p>open localhost:9021 then goto HOME/Connect-Cluster/Connectors</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TOBOC5XlSNXrTmeANRBt5A.png" /></figure><p>add a new connector</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d913p8zr0KD_2pJKQozMcQ.png" /></figure><p>in this case we choose HdfsSinkConnector, and configure the connector as below. “key.converter.schemas.enable” and “value.converter.schemas.enable” are additional property that needs to be added at the bottom of the page</p><pre>{<br>  &quot;name&quot;: &quot;HdfsSinkConnectorConnector_0&quot;,<br>  &quot;config&quot;: {<br>    &quot;key.converter.schemas.enable&quot;: &quot;false&quot;,<br>    &quot;value.converter.schemas.enable&quot;: &quot;false&quot;,<br>    &quot;name&quot;: &quot;HdfsSinkConnectorConnector_0&quot;,<br>    &quot;connector.class&quot;: &quot;io.confluent.connect.hdfs.HdfsSinkConnector&quot;,<br>    &quot;key.converter&quot;: &quot;org.apache.kafka.connect.json.JsonConverter&quot;,<br>    &quot;value.converter&quot;: &quot;org.apache.kafka.connect.json.JsonConverter&quot;,<br>    &quot;topics&quot;: &quot;data-sink-hdfs&quot;,<br>    &quot;hdfs.url&quot;: &quot;hdfs://namenode:9000&quot;,  # hostname, see docker-compose.yaml<br>    &quot;flush.size&quot;: &quot;1&quot;<br>  }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/826/1*xIPswqIZ7GV1zpYT5pW04A.png" /></figure><h4>Test</h4><p>produce a message</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1VDwEShBGAhYqB4nvvHscA.png" /></figure><p>docker attach to namenode then check</p><pre>docker exec -it namenode bash<br>root@f3ab6c37360e:/# hadoop fs -ls /<br>Found 3 items<br>drwxr-xr-x   - appuser supergroup          0 2023-10-17 14:51 /logs<br>drwxr-xr-x   - root    supergroup          0 2023-10-16 15:42 /rmstate<br>drwxr-xr-x   - appuser supergroup          0 2023-10-17 14:51 /topics<br><br># /topics is the default destination<br>hadoop fs -ls /topics/data-sink-hdfs/partition=0<br><br>Found 3 items<br>-rw-r--r--   3 appuser supergroup       1048 2023-10-17 14:51 /topics/data-sink-hdfs/partition=0/data-sink-hdfs+0+0000000000+0000000000.avro<br>-rw-r--r--   3 appuser supergroup       1048 2023-10-17 14:51 /topics/data-sink-hdfs/partition=0/data-sink-hdfs+0+0000000001+0000000001.avro<br>-rw-r--r--   3 appuser supergroup       1046 2023-10-17 15:06 /topics/data-sink-hdfs/partition=0/data-sink-hdfs+0+0000000002+0000000002.avro<br><br># we produce 3 messages</pre><p>In the next article, I will explore how to save the message in parquet format and use data visualization tools to query on parquet file.</p><h3>reference</h3><ul><li>for hadoop docker, refer to this <a href="https://github.com/big-data-europe/docker-hadoop">https://github.com/big-data-europe/docker-hadoop</a></li><li>for kafka docker yaml, refer to <a href="https://github.com/confluentinc/cp-all-in-one/tree/7.5.0-post/cp-all-in-one-community">https://github.com/confluentinc/cp-all-in-one/tree/7.5.0-post/cp-all-in-one-community</a></li><li>All the files I used are <a href="https://github.com/softarts/data-sink-dataplatform">here</a></li></ul><p><strong><em>Happy Coding!</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a8236dfab7bc" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/real-time-message-ingestion-to-big-data-platform-a8236dfab7bc">Real-Time Message Ingestion to Big Data Platform</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JVM OOM in Kubernetes POD with small memory allocated]]></title>
            <link>https://medium.com/javarevisited/jvm-oom-in-kubernetes-pod-with-small-memory-allocated-64eab77e6223?source=rss-8689b3073a6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/64eab77e6223</guid>
            <category><![CDATA[jvm]]></category>
            <category><![CDATA[threaddump]]></category>
            <category><![CDATA[oom]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[heapdump]]></category>
            <dc:creator><![CDATA[Rui Zhou]]></dc:creator>
            <pubDate>Mon, 16 Oct 2023 15:34:44 GMT</pubDate>
            <atom:updated>2023-10-18T06:37:33.845Z</atom:updated>
            <content:encoded><![CDATA[<p>How to configure JVM memory on POD with a small memory resource.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*tipgcjaKux0rHdp3" /><figcaption>Photo by <a href="https://unsplash.com/@pawel_czerwinski?utm_source=medium&amp;utm_medium=referral">Pawel Czerwinski</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Problem</h3><p>A Kubernetes POD is configured with max 1000 MB memory and 1 CPU. Recently high memory usage has been observed(90%+) and results in POD being killed by OOM. In this article, I will explain how I solve this issue.</p><h3>Tools</h3><p>The first thing is we need to understand the application’s current memory usage. We can use a range of tools to track the memory and heap usage. Refer to <a href="https://www.baeldung.com/java-heap-dump-capture">https://www.baeldung.com/java-heap-dump-capture</a>. However, there are some restrictions.</p><h4>GUI</h4><p><a href="https://docs.oracle.com/javase/8/docs/technotes/guides/management/jconsole.html">jconsole</a> and <a href="https://visualvm.github.io/">visualvm </a>are powerful tools to monitor memory usage, but in a Kubernetes cluster environment, usually we don’t have permission to connect to remote JVM/JMX port.</p><h4>Command line tool</h4><p>In case we can SSH to POD and have JDK tools installed, we can use <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jstat.html">jstat </a>to print the memory usage.</p><pre># pid is 1 in POD, print every 10000 ms<br>jstat -gc -t 1 10000<br>S0C    S1C    S0U   S1U      EC          EU           OC          OU        MC        MU     CCSC  CCSU  YGC   YGCT  FGC  FGCT   GCT<br>512.0  512.0   0.0    32.0  31232.0  18740.0   175104.0    304.1    4864.0  2435.9  512.0   266.9     297    0.591   0      0.000    0.591<br>512.0  512.0   0.0    64.0  31232.0  19989.0   175104.0    304.1    4864.0  2435.9  512.0   266.9     301    0.591   0      0.000    0.591<br>512.0  512.0   32.0   0.0   31232.0   4372.6    175104.0    304.1    4864.0  2435.9  512.0   266.9     310    0.603   0      0.000    0.603<br>512.0  512.0   32.0   0.0   31232.0  13117.7   175104.0    304.1    4864.0  2435.9  512.0   266.9     314    0.609   0      0.000    0.609</pre><p>and use jcmd to retrieve heap dump:</p><pre>./jcmd 1 GC.heap_dump /path/to/heapdump-file<br># run the below command at local machine, to copy heapdump fiel to local machine<br>kubectl cp mypod-name:/path/to/heapdump-file ./local-file-name -n namespace <br><br># or print heap info<br>./jcmd 1 GC.heap_info<br><br># or print native memory statistic(must enable native memory summary in java command line)<br>./jcmd 1 VM.native_memory summary</pre><h4>Observability</h4><p>In Cloud native applications, we often use Spring actuator, Jolokia, micrometer, and Prometheus to produce the application JVM metrics, On the other side we use collectors such as Telegraf, Prometheus and inject metrics into sumologic, datadog, etc.</p><h4>GC log</h4><p>we can also add this to the Java application command line to enable GC activities log at runtime.</p><pre>&quot;-Xlog:gc*=info&quot;<br># or mode detail<br>&quot;-Xlog:gc*=debug&quot;</pre><p>example =&gt;</p><pre>2021-10-07T14:13:10.847+0800: [Full GC (Metadata GC Threshold) [PSYoungGen: 384K-&gt;0K(93696K)] [ParOldGen: 982K-&gt;1360K(1172480K)] 1366K-&gt;1360K(1266176K), [Metaspace: 410659K-&gt;410659K(456704K)], 0.0418110 secs] [Times: user=0.04 sys=0.00, real=0.04 secs]</pre><h4>Use Code</h4><p>or we can use the below code to get more pool details. e.g. direct pool CodeCache Compressed Class Space etc. it will print the statistic every 10 seconds. The different memory categories will be explained next.</p><p><strong>MemoryMonitor.kt</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/48578ec3389023c506b15d4419e0d1a5/href">https://medium.com/media/48578ec3389023c506b15d4419e0d1a5/href</a></iframe><h3>JVM memory configuration</h3><h4>Dockerfile</h4><pre>FROM azul/zulu-openjdk:17.0.7-17.42.19-jre<br><br>ARG DEPENDENCY=build/dependency<br>COPY --chown=userXX:userXX ${DEPENDENCY}/BOOT-INF/lib /app/lib<br>COPY --chown=userXX:userXX ${DEPENDENCY}/META-INF /app/META-INF/<br>COPY --chown=userXX:userXX ${DEPENDENCY}/BOOT-INF/classes /app/<br><br>ENTRYPOINT [&quot;java&quot;, \<br>    &quot;-server&quot;, \<br>    &quot;-cp&quot;, &quot;/app:/app/lib/*&quot;, \<br>    &quot;-XX:MaxRAMPercentage=60.0&quot;, \<br>    &quot;-XX:+UnlockDiagnosticVMOptions&quot;, \<br>    &quot;-XX:NativeMemoryTracking=summary&quot;, \<br>    &quot;-XX:+PrintNMTStatistics&quot;, \<br>    &quot;com.company.myapp.ApplicationKt&quot;, \<br>    &quot;--spring.config.location=classpath:/application.yaml&quot;]<br></pre><p>The above Dockerfile uses a max of 60% RAM(e.g. we configured a total of 1 GB for POD) and enables native memory tracking. which is equal to 600 MB for heap usage, and around the remaining 400 MB for other usage(e.g. non-heap, JVM itself, and OS), look good? actually, 400 MB is far less than enough in nowadays modern cloud-native applications.</p><h4>memory usage category</h4><p>This table lists several important memory categories.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5HtDJFzgiYg-mJFY6KnEow.png" /></figure><p><strong>Metaspace</strong>, formerly known as PermGen (Permanent Generation), is a non-heap memory area that stores class metadata, constant pool information, and method bytecode. It was introduced in Java 8 as a replacement for PermGen, which was removed due to memory management issues.</p><p>It is unlimited if not specified. my observation is it can easily hit 200–300MB in spring boot application</p><p><strong>Direct Buffer</strong>, is also unlimited<strong>, </strong>used in socket, nio. In general Spring application usually occupies just a small amount of memory(20~40MB), but in some Big data applications such as Spark, it may use a huge amount and cause OOM!</p><p>with the above Dockerfile setting, <strong>CodeCache </strong>and <strong>CompressedClassSpace limits </strong>are 256 MB and 64 MB respectively, the actual used will be &lt;50 MB together.</p><h3>Solution</h3><p>We need to be aware that JVM and OS itself also have memory overhead. I would say it is about 25~30% of total memory. So the total memory consumption :</p><p><em>heap(600 MB) + metaspace(250 MB) + codecache and compressed_class_space(50 MB) + OS&amp;JVM(300 MB,30% of total) = </em><strong><em>1200 MB,</em> which exceeds the POD 1 GB limit!</strong></p><p>In this case, it is not caused by a memory leak, hence we need to calculate and allocate memory accordingly.</p><ol><li>increase the POD limit to 2 GB, this is a safer and easiest way</li><li>lower the MaxRAMPercentage to 40.0%, and/or limit the other non-heap usage, you can tweak the number according to what you observe in the log:</li></ol><pre>&quot;-XX:MaxRAMPercentage=40.0&quot;, \<br>&quot;-XX:MaxMetaspaceSize=176m&quot;, \<br>&quot;-XX:CompressedClassSpaceSize=32m&quot;, \<br>&quot;-XX:ReservedCodeCacheSize=48m&quot;, \<br>&quot;-XX:MaxDirectMemorySize=24m&quot;,</pre><p>Hence we get:</p><p><em>heap(400 MB) + non-heap(280 MB: metaspace + codecache+compressed class…) =&gt; we left 320 MB room for OS/JVM, barely enough :)</em></p><h3>One more thing, Thread dump analytics</h3><p>Recently I encountered another thread leak issue: <a href="https://github.com/harness/ff-java-server-sdk/pull/107">https://github.com/harness/ff-java-server-sdk/pull/107</a></p><p>If the thread count(from the above code) increases dramatically, we may want to analyze the thread dump.</p><pre>jstack -l 1 &gt; thread-dump-1.txt<br># copy to local machine<br># wait until the thread count changes significantly<br>jstack -l 1 &gt; thread-dump-2.txt</pre><p>I use this online tool to compare the dump: <a href="https://jstack.review/">https://jstack.review/</a></p><p>refer to <a href="https://www.baeldung.com/java-analyze-thread-dumps">https://www.baeldung.com/java-analyze-thread-dumps</a></p><p><strong><em>Happy Coding!</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=64eab77e6223" width="1" height="1" alt=""><hr><p><a href="https://medium.com/javarevisited/jvm-oom-in-kubernetes-pod-with-small-memory-allocated-64eab77e6223">JVM OOM in Kubernetes POD with small memory allocated</a> was originally published in <a href="https://medium.com/javarevisited">Javarevisited</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Build a cloud-native application with Buildpacks]]></title>
            <link>https://medium.com/@wirelesser/build-a-cloud-native-application-with-buildpacks-2f6dd87fc368?source=rss-8689b3073a6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/2f6dd87fc368</guid>
            <category><![CDATA[paketo]]></category>
            <category><![CDATA[buildpack]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[cloud-native-buildpacks]]></category>
            <dc:creator><![CDATA[Rui Zhou]]></dc:creator>
            <pubDate>Sat, 30 Sep 2023 02:51:14 GMT</pubDate>
            <atom:updated>2023-09-30T02:51:14.218Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VmpX2RHxJ2fy2tJo" /><figcaption>Photo by <a href="https://unsplash.com/@deepavalig?utm_source=medium&amp;utm_medium=referral">Deepavali Gaind</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p><a href="https://buildpacks.io/docs/concepts/components/buildpack">Buildpacks</a> can transform your application source code into modern container standards images that can run on any cloud without Dockerfile. In this article, I will explain a couple of examples.</p><p><a href="https://buildpacks.io/docs/concepts/components/buildpack">Buildpacks</a> provide framework and runtime support for applications. Buildpacks examine your apps to determine all the dependencies it needs and configure them appropriately to run on any cloud.</p><h3>Example</h3><p>No need to write any Dockerfile or install any tool, just start the docker environment and build it</p><pre>gradlew.bat bootBuildImage<br><br>&gt; Task :bootBuildImage<br>Building image &#39;docker.io/library/template-service:1.0&#39;<br><br> &gt; Pulling builder image &#39;docker.io/paketobuildpacks/builder:base&#39; ..................................................<br> &gt; Pulled builder image &#39;paketobuildpacks/builder@sha256:17ea21162ba8c7717d3ead3ee3836a368aced7f02f2e59658e52029bd6d149e7&#39;<br> &gt; Pulling run image &#39;docker.io/paketobuildpacks/run:base-cnb&#39; ..................................................<br> &gt; Pulled run image &#39;paketobuildpacks/run@sha256:1af9935d8987fd52b2266d288200c9482d1dd5529860bbf5bc2d248de1cb1a38&#39;<br> &gt; Executing lifecycle version v0.16.5<br> &gt; Using build cache volume &#39;pack-cache-c0553dfeb43b.build&#39;<br><br> &gt; Running creator<br>    [creator]     ===&gt; ANALYZING<br>    [creator]     Restoring data for SBOM from previous image<br>    [creator]     ===&gt; DETECTING<br>    [creator]     6 of 26 buildpacks participating<br>    [creator]     paketo-buildpacks/ca-certificates   3.6.3<br>    [creator]     paketo-buildpacks/bellsoft-liberica 10.2.6<br>    [creator]     paketo-buildpacks/syft              1.32.1<br>    [creator]     paketo-buildpacks/executable-jar    6.7.4<br>    [creator]     paketo-buildpacks/dist-zip          5.6.4<br>    [creator]     paketo-buildpacks/spring-boot       5.26.1<br>    [creator]     ===&gt; RESTORING<br>    [creator]     Restoring metadata for &quot;paketo-buildpacks/ca-certificates:helper&quot; from app image<br>    [creator]     Restoring metadata for &quot;paketo-buildpacks/bellsoft-liberica:helper&quot; from app image<br>    [creator]     Restoring metadata for &quot;paketo-buildpacks/bellsoft-liberica:java-security-properties&quot; from app image<br>    [creator]     Restoring metadata for &quot;paketo-buildpacks/bellsoft-liberica:jre&quot; from app image<br>    [creator]     Restoring metadata for &quot;paketo-buildpacks/syft:syft&quot; from cache<br>    [creator]     Restoring metadata for &quot;paketo-buildpacks/spring-boot:helper&quot; from app image<br>    [creator]     Restoring metadata for &quot;paketo-buildpacks/spring-boot:spring-cloud-bindings&quot; from app image<br>    [creator]     Restoring metadata for &quot;paketo-buildpacks/spring-boot:web-application-type&quot; from app image<br>    [creator]     Restoring data for &quot;paketo-buildpacks/syft:syft&quot; from cache<br>    [creator]     Restoring data for SBOM from cache<br>    [creator]     ===&gt; BUILDING<br>    [creator]<br>    [creator]     Paketo Buildpack for CA Certificates 3.6.3<br>    [creator]       https://github.com/paketo-buildpacks/ca-certificates<br>    [creator]       Launch Helper: Reusing cached layer<br>    [creator]<br>    [creator]     Paketo Buildpack for BellSoft Liberica 10.2.6<br>    [creator]       https://github.com/paketo-buildpacks/bellsoft-liberica<br>    [creator]       Build Configuration:<br>    [creator]         $BP_JVM_JLINK_ARGS           --no-man-pages --no-header-files --strip-debug --compress=1  configure custom link arguments (--output must be omitted)<br>    [creator]         $BP_JVM_JLINK_ENABLED        false                                                        enables running jlink tool to generate custom JRE<br>    [creator]         $BP_JVM_TYPE                 JRE                                                          the JVM type - JDK or JRE<br>    [creator]         $BP_JVM_VERSION              17                                                           the Java version<br>    [creator]       Launch Configuration:<br>    [creator]         $BPL_DEBUG_ENABLED           false                                                        enables Java remote debugging support<br>    [creator]         $BPL_DEBUG_PORT              8000                                                         configure the remote debugging port<br>    [creator]         $BPL_DEBUG_SUSPEND           false                                                        configure whether to suspend execution until a debugger has attached<br>    [creator]         $BPL_HEAP_DUMP_PATH                                                                       write heap dumps on error to this path<br>    [creator]         $BPL_JAVA_NMT_ENABLED        true                                                         enables Java Native Memory Tracking (NMT)<br>    [creator]         $BPL_JAVA_NMT_LEVEL          summary                                                      configure level of NMT, summary or detail<br>    [creator]         $BPL_JFR_ARGS                                                                             configure custom Java Flight Recording (JFR) arguments<br>    [creator]         $BPL_JFR_ENABLED             false                                                        enables Java Flight Recording (JFR)<br>    [creator]         $BPL_JMX_ENABLED             false                                                        enables Java Management Extensions (JMX)<br>    [creator]         $BPL_JMX_PORT                5000                                                         configure the JMX port<br>    [creator]         $BPL_JVM_HEAD_ROOM           0                                                            the headroom in memory calculation<br>    [creator]         $BPL_JVM_LOADED_CLASS_COUNT  35% of classes                                               the number of loaded classes in memory calculation<br>    [creator]         $BPL_JVM_THREAD_COUNT        250                                                          the number of threads in memory calculation<br>    [creator]         $JAVA_TOOL_OPTIONS                                                                        the JVM launch flags<br>    [creator]         Using Java version 17 extracted from MANIFEST.MF<br>    [creator]       BellSoft Liberica JRE 17.0.7: Reusing cached layer<br>    [creator]       Launch Helper: Reusing cached layer<br>    [creator]       Java Security Properties: Reusing cached layer<br>    [creator]<br>    [creator]     Paketo Buildpack for Syft 1.32.1<br>    [creator]       https://github.com/paketo-buildpacks/syft<br>    [creator]         Downloading from https://github.com/anchore/syft/releases/download/v0.84.0/syft_0.84.0_linux_amd64.tar.gz<br>    [creator]         Verifying checksum<br>    [creator]         Writing env.build/SYFT_CHECK_FOR_APP_UPDATE.default<br>    [creator]<br>    [creator]     Paketo Buildpack for Executable JAR 6.7.4<br>    [creator]       https://github.com/paketo-buildpacks/executable-jar<br>    [creator]       Class Path: Contributing to layer<br>    [creator]         Writing env/CLASSPATH.delim<br>    [creator]         Writing env/CLASSPATH.prepend<br>    [creator]       Process types:<br>    [creator]         executable-jar: java org.springframework.boot.loader.JarLauncher (direct)<br>    [creator]         task:           java org.springframework.boot.loader.JarLauncher (direct)<br>    [creator]         web:            java org.springframework.boot.loader.JarLauncher (direct)<br>    [creator]<br>    [creator]     Paketo Buildpack for Spring Boot 5.26.1<br>    [creator]       https://github.com/paketo-buildpacks/spring-boot<br>    [creator]       Build Configuration:<br>    [creator]         $BP_SPRING_CLOUD_BINDINGS_DISABLED   false  whether to contribute Spring Boot cloud bindings support<br>    [creator]       Launch Configuration:<br>    [creator]         $BPL_SPRING_CLOUD_BINDINGS_DISABLED  false  whether to auto-configure Spring Boot environment properties from bindings<br>    [creator]         $BPL_SPRING_CLOUD_BINDINGS_ENABLED   true   Deprecated - whether to auto-configure Spring Boot environment properties from bindings<br>    [creator]       Creating slices from layers index<br>    [creator]         dependencies (69.7 MB)<br>    [creator]         spring-boot-loader (269.4 KB)<br>    [creator]         snapshot-dependencies (0.0 B)<br>    [creator]         application (54.9 KB)<br>    [creator]       Launch Helper: Reusing cached layer<br>    [creator]       Spring Cloud Bindings 1.13.0: Contributing to layer<br>    [creator]         Downloading from https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-bindings/1.13.0/spring-cloud-bindings-1.13.0.jar<br>    [creator]         Verifying checksum<br>    [creator]         Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings<br>    [creator]       Web Application Type: Reusing cached layer<br>    [creator]       4 application slices<br>    [creator]       Image labels:<br>    [creator]         org.opencontainers.image.title<br>    [creator]         org.opencontainers.image.version<br>    [creator]         org.springframework.boot.version<br>    [creator]     ===&gt; EXPORTING<br>    [creator]     Reusing layer &#39;paketo-buildpacks/ca-certificates:helper&#39;<br>    [creator]     Reusing layer &#39;paketo-buildpacks/bellsoft-liberica:helper&#39;<br>    [creator]     Reusing layer &#39;paketo-buildpacks/bellsoft-liberica:java-security-properties&#39;<br>    [creator]     Reusing layer &#39;paketo-buildpacks/bellsoft-liberica:jre&#39;<br>    [creator]     Reusing layer &#39;paketo-buildpacks/executable-jar:classpath&#39;<br>    [creator]     Reusing layer &#39;paketo-buildpacks/spring-boot:helper&#39;<br>    [creator]     Reusing layer &#39;paketo-buildpacks/spring-boot:spring-cloud-bindings&#39;<br>    [creator]     Reusing layer &#39;paketo-buildpacks/spring-boot:web-application-type&#39;<br>    [creator]     Reusing layer &#39;buildpacksio/lifecycle:launch.sbom&#39;<br>    [creator]     Reusing 5/5 app layer(s)<br>    [creator]     Reusing layer &#39;buildpacksio/lifecycle:launcher&#39;<br>    [creator]     Reusing layer &#39;buildpacksio/lifecycle:config&#39;<br>    [creator]     Reusing layer &#39;buildpacksio/lifecycle:process-types&#39;<br>    [creator]     Adding label &#39;io.buildpacks.lifecycle.metadata&#39;<br>    [creator]     Adding label &#39;io.buildpacks.build.metadata&#39;<br>    [creator]     Adding label &#39;io.buildpacks.project.metadata&#39;<br>    [creator]     Adding label &#39;org.opencontainers.image.title&#39;<br>    [creator]     Adding label &#39;org.opencontainers.image.version&#39;<br>    [creator]     Adding label &#39;org.springframework.boot.version&#39;<br>    [creator]     Setting default process type &#39;web&#39;<br>    [creator]     Saving docker.io/library/template-service:1.0...<br>    [creator]     *** Images (b2ff10a10f98):<br>    [creator]           docker.io/library/template-service:1.0<br>    [creator]     Reusing cache layer &#39;paketo-buildpacks/syft:syft&#39;<br>    [creator]     Reusing cache layer &#39;buildpacksio/lifecycle:cache.sbom&#39;<br><br>Successfully built image &#39;docker.io/library/template-service:1.0&#39;</pre><p>image created</p><pre>docker images<br>REPOSITORY                 TAG                   IMAGE ID       CREATED        SIZE<br>template-service           1.0                   b2ff10a10f98   43 years ago   330MB<br>paketobuildpacks/builder   base                  050ed48532b2   43 years ago   1.31GB</pre><p>Run</p><pre>docker run -it --env POSTGRES_DB=jdbc:postgresql://host.docker.internal:5432/template-service template-service:1.0<br>Setting Active Processor Count to 12<br>Calculating JVM memory based on 22731180K available memory<br>For more information on this calculation, see https://paketo.io/docs/reference/java-reference/#memory-calculator<br>Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx22090408K -XX:MaxMetaspaceSize=128771K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 22731180K, Thread Count: 250, Loaded Class Count: 20321, Headroom: 0%)<br>Enabling Java Native Memory Tracking<br>Adding 137 container CA certificates to JVM truststore<br>Spring Cloud Bindings Enabled<br>Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:ActiveProcessorCount=12 -XX:MaxDirectMemorySize=10M -Xmx22090408K -XX:MaxMetaspaceSize=128771K -XX:ReservedCodeCacheSize=240M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -Dorg.springframework.cloud.bindings.boot.enable=true<br>2023-09-17T15:36:27.814Z  INFO 1 --- [           main] c.tower.templateservice.ApplicationKt    : Starting ApplicationKt v1.0 using Java 17.0.7 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)<br>2023-09-17T15:36:27.820Z  INFO 1 --- [           main] c.tower.templateservice.ApplicationKt    : The following 1 profile is active: &quot;message-debug-logging&quot;<br>2023-09-17T15:36:28.395Z  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.<br>2023-09-17T15:36:28.456Z  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 44 ms. Found 1 JPA repository interfaces.<br>2023-09-17T15:36:29.272Z  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)<br>2023-09-17T15:36:29.284Z  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]<br>2023-09-17T15:36:29.284Z  INFO 1 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.12]<br>2023-09-17T15:36:29.389Z  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext<br>2023-09-17T15:36:29.391Z  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1480 ms<br>2023-09-17T15:36:29.566Z  INFO 1 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]<br>2023-09-17T15:36:29.642Z  INFO 1 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.2.7.Final<br>2023-09-17T15:36:29.645Z  INFO 1 --- [           main] org.hibernate.cfg.Environment            : HHH000406: Using bytecode reflection optimizer<br>2023-09-17T15:36:29.894Z  INFO 1 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy<br>2023-09-17T15:36:30.038Z  INFO 1 --- [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer<br>2023-09-17T15:36:30.260Z  INFO 1 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy<br>2023-09-17T15:36:30.841Z  INFO 1 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]<br>2023-09-17T15:36:30.860Z  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...<br>2023-09-17T15:36:31.029Z  INFO 1 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@625db0e0<br>2023-09-17T15:36:31.032Z  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.<br>2023-09-17T15:36:31.143Z  INFO 1 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit &#39;default&#39;<br>2023-09-17T15:36:32.091Z  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path &#39;&#39;<br>2023-09-17T15:36:32.107Z  INFO 1 --- [           main] c.tower.templateservice.ApplicationKt    : Started ApplicationKt in 4.914 seconds (process running for 5.465)</pre><h3>Build behind firewall</h3><p>In case it can’t be built behind the firewall:</p><pre>BellSoft Liberica JRE 17.0.7: Contributing to layer<br>    [creator]         Downloading from https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jre17.0.7+7-linux-amd64.tar.gz<br>unable to invoke layer<br>unable to get dependency jre<br>unable to download https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jre17.0.7+7-linux-amd64.tar.gz<br>unable to request xxx<br>Get &quot;https://xxxx&quot;<br>ERROR: failed to build: exit status 1</pre><p>add these to build.gradle :</p><pre>tasks.named(&quot;bootBuildImage&quot;) {<br>    bindings = [&quot;${projectDir}/bindings/ca-certificates:/platform/bindings/ca-certificates&quot;]<br>}</pre><p>and</p><pre>mkdir -p bindings/ca-certificates<br>cp path/to/your-cert.pem bindings/ca-certificates<br>echo &quot;ca-certificates&quot; &gt; bindings/ca-certificates/type</pre><p>will see an additional cert added;</p><pre>[creator]     Paketo Buildpack for CA Certificates 3.6.3<br>[creator]       https://github.com/paketo-buildpacks/ca-certificates<br>[creator]       Launch Helper: Reusing cached layer<br>[creator]       CA Certificates: Contributing to layer<br>[creator]         Added 1 additional CA certificate(s) to system truststore<br>[creator]         Writing env.build/SSL_CERT_DIR.append<br>[creator]         Writing env.build/SSL_CERT_DIR.delim<br>[creator]         Writing env.build/SSL_CERT_FILE.default</pre><h4>When a dependent package is blocked by firewall</h4><p>If a package is not accessible(blocked by firewall), we can tell buildpack to look for it from a self-hosted artifactory</p><p>e.g. spring-cloud-bindings@1.13.0 , find this package’s sha256 in <a href="https://github.com/paketo-buildpacks/spring-boot/blob/main/buildpack.toml">https://github.com/paketo-buildpacks/spring-boot/blob/main/buildpack.toml</a></p><pre>  [[metadata.dependencies]]<br>    cpes = [&quot;cpe:2.3:a:vmware:spring_cloud_bindings:1.13.0:*:*:*:*:*:*:*&quot;]<br>    id = &quot;spring-cloud-bindings&quot;<br>    name = &quot;Spring Cloud Bindings&quot;<br>    purl = &quot;pkg:generic/springframework/spring-cloud-bindings@1.13.0&quot;<br>    sha256 = &quot;70a448cd45d1dbc117770f934961cd9577c0c4404d34986824f8f593cae4aada&quot;<br>    stacks = [&quot;io.buildpacks.stacks.bionic&quot;, &quot;io.paketo.stacks.tiny&quot;, &quot;*&quot;]<br>    uri = &quot;https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-bindings/1.13.0/spring-cloud-bindings-1.13.0.jar&quot;<br>    version = &quot;1.13.0&quot;</pre><p>Then we do this:</p><pre>echo &quot;https://${jfrog_user}:{jfrpg_pass}@my-artifactory-hostname/artifactory/jcenter-cache/org/spring/packagename/filename&quot;    &gt;&gt; bindings/spring/spring-cloud-config/70a448cd45d1dbc117770f934961cd9577c0c4404d34986824f8f593cae4aada<br>echo &quot;dependency-mapping&quot; &gt;&gt; bindings/spring/spring-cloud-config/type</pre><p>the folder structure will be:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/841/1*qdsxJ0lopnBtYoH1PdJnqg.png" /></figure><p>refer to <a href="https://github.com/paketo-buildpacks/gradle">https://github.com/paketo-buildpacks/gradle</a></p><h3>Choose Java version</h3><p>change BP_JVM_VERSION in build.gradle,</p><pre>tasks.named(&quot;bootBuildImage&quot;) {<br>    bindings = [&quot;${projectDir}/bindings/ca-certificates:/platform/bindings/ca-certificates&quot;]<br>    environment = [&quot;BP_JVM_VERSION&quot;: &quot;20&quot;]<br>}</pre><p>We can also specify image name in command line</p><pre>gradlew.bat bootBuildImage --imageName=template-service-name:local</pre><p>Refer to <a href="https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/">spring gradle plugin reference</a> for More information</p><h3>Build with <strong>PACK CLI</strong></h3><p>an example:</p><pre>pack build template-service --builder paketobuildpacks/builder-jammy-base  --volume H:\work\template-service\bindings\ca-certificates:/platform/bindings/ca-certificates --env &quot;BP_JVM_VERSION=17&quot;</pre><p>but seems it doesn’t support mavenLocal very well</p><pre> Repositories:<br>      Name: MavenLocal; url: file:/home/cnb/.m2/repository<br><br>&gt; Task :compileKotlin FAILED<br><br>      FAILURE: Build failed with an exception.<br><br>      * What went wrong:<br>      Execution failed for task &#39;:compileKotlin&#39;.<br>      &gt; Could not resolve all files for configuration &#39;:compileClasspath&#39;.<br>         &gt; Could not find com.company:mylib:1.0.0.  (supposed in mavenLocal)<br>           Required by:<br>               project :<br><br>      * Try:<br>      &gt; Run with --stacktrace option to get the stack trace.<br>      &gt; Run with --info or --debug option to get more log output.<br>      &gt; Run with --scan to get full insights.</pre><p>I prefer to gradle-plugin , which is better platform-independent wise.</p><h3>Rebase</h3><p>Rebase allows app developers or operators to rapidly update an app image when its stack’s run image has changed. By using image layer rebasing, this command avoids the need to fully rebuild the app.</p><p><a href="https://buildpacks.io/docs/concepts/operations/rebase/">Rebase · Cloud Native Buildpacks</a></p><p>example：</p><pre>pack inspect-image <br># =&gt;<br>pack inspect-image template-service:1.0<br>Inspecting image: template-service:1.0<br><br>REMOTE:<br>(not present)<br><br>LOCAL:<br><br>Stack: io.buildpacks.stacks.bionic<br><br>Base Image:<br>  Reference: f2e5000af0cb0db7d16a2939fc88c2aa522e91826c0e87ef2a4ec2825b213585<br>  Top Layer: sha256:1eb5983d73014e2f11902c90f9af0f0dd6b4b1cb128a57b1cebee6ef54252199<br><br>Run Images:<br>  index.docker.io/paketobuildpacks/run:base-cnb<br>  gcr.io/paketo-buildpacks/run:base-cnb<br><br>Rebasable: true<br><br>Buildpacks:<br>  ID                                         VERSION        HOMEPAGE<br>  paketo-buildpacks/ca-certificates          3.6.3          https://github.com/paketo-buildpacks/ca-certificates<br>  paketo-buildpacks/bellsoft-liberica        10.2.6         https://github.com/paketo-buildpacks/bellsoft-liberica<br>  paketo-buildpacks/syft                     1.32.1         https://github.com/paketo-buildpacks/syft<br>  paketo-buildpacks/executable-jar           6.7.4          https://github.com/paketo-buildpacks/executable-jar<br>  paketo-buildpacks/dist-zip                 5.6.4          https://github.com/paketo-buildpacks/dist-zip<br>  paketo-buildpacks/spring-boot              5.26.1         https://github.com/paketo-buildpacks/spring-boot<br><br>Processes:<br>  TYPE                  SHELL        COMMAND        ARGS                                               WORK DIR<br>  web (default)                      java           org.springframework.boot.loader.JarLauncher        /workspace<br>  executable-jar                     java           org.springframework.boot.loader.JarLauncher        /workspace<br>  task                               java           org.springframework.boot.loader.JarLauncher        /workspace</pre><p>rebase:</p><pre># need admin privilege on windows<br>pack rebase template-service:1.0 <br>#=&gt;<br><br>base-cnb: Pulling from paketobuildpacks/run<br>Digest: sha256:1af9935d8987fd52b2266d288200c9482d1dd5529860bbf5bc2d248de1cb1a38<br>Status: Image is up to date for paketobuildpacks/run:base-cnb<br>Rebasing template-service:1.0 on run image index.docker.io/paketobuildpacks/run:base-cnb<br>Timer: Rebaser started at 2023-09-19T00:43:54+08:00<br>Timer: Saving template-service:1.0... started at 2023-09-19T00:43:59+08:00<br>*** Images (82c2cca9d640):<br>      template-service:1.0<br>Timer: Saving template-service:1.0... ran for 1.8416996s and ended at 2023-09-19T00:44:01+08:00<br>Timer: Rebaser ran for 6.9762501s and ended at 2023-09-19T00:44:01+08:00<br>Rebased Image: 82c2cca9d640d8401f45bf651b11f5e6ca689e476adf10f1fa1c59faba1f5f19<br>Successfully rebased image template-service:1.0</pre><h3>Reference</h3><p><a href="https://buildpacks.io/docs/">https://buildpacks.io/docs/</a></p><p><a href="https://paketo.io/docs/">Getting Started</a></p><p><strong><em>Happy Coding!</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2f6dd87fc368" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A shell script to check JDK and spring-boot version at compile time]]></title>
            <link>https://medium.com/@wirelesser/a-shell-script-to-check-jdk-and-spring-boot-version-at-compile-time-4acd7ee4ef6?source=rss-8689b3073a6e------2</link>
            <guid isPermaLink="false">https://medium.com/p/4acd7ee4ef6</guid>
            <category><![CDATA[shell-script]]></category>
            <category><![CDATA[gradlew]]></category>
            <category><![CDATA[jdk-17]]></category>
            <category><![CDATA[spring-boot]]></category>
            <dc:creator><![CDATA[Rui Zhou]]></dc:creator>
            <pubDate>Tue, 18 Jul 2023 08:00:43 GMT</pubDate>
            <atom:updated>2023-07-18T08:00:43.285Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*kscycAmZtfGSGErx" /><figcaption>Photo by <a href="https://unsplash.com/@neonbrand?utm_source=medium&amp;utm_medium=referral">Kenny Eliason</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>I wrote a <a href="https://medium.com/@wirelesser/a-shell-script-to-get-all-projects-spring-boot-version-c286c678d061">story</a> about getting the spring boot version, I want to check the JDK version at compile time as well</p><pre><br># A quick shell script to check the spring-boot version<br># only work for gradlw project!!<br># read repo-name from services.csv<br># cat services.csv <br># camel-spring-demo,team,production<br><br>set -e<br>set -x<br>mkdir -p tmp<br><br>servicelist=$(pwd)/services.csv # reponame,squad,env(prod/dev)<br>output=$(pwd)/spring-ver.csv<br>repo_base=&quot;https://github.com/myaccount/&quot;  # my repo root path<br>kt=/path/to/kubectl<br>JDK17=/path/to/jdk17/home<br>JDK11=/path/to/jdk11/home<br>JDK8=/path/to/jdk8/home<br><br># &quot;https://$username:$pass@github.com/myaccount/&quot;  # it may need username/pass<br><br># pull all code under ./tmp/<br>cd ./tmp/<br><br>while IFS=&quot;,&quot; read -r varservice varsquad varenv<br>do<br>  # skip line start with &#39;#&#39; <br>  [[ $line =~ ^#.* ]] &amp;&amp; continue<br>  repo=&quot;$repo_base$varservice.git&quot;<br>  echo &quot;$varenv : $varsquad :$varservice : $repo&quot;<br><br>  if [ -d $varservice ];then<br>    echo &quot;Directory $varservice exists.&quot;<br>    cd $varservice<br>    git pull<br>    cd ..<br>  else<br>    git clone --depth=1 $repo<br>  fi<br><br>  if [ -f &quot;$varservice/gradlew&quot; ];then<br>    echo &quot;running gradlew&quot;<br>    jdkbuildenv=$(grep sourceCompatibility $varservice/build.gradle |head -n 1|awk -F &quot;=&quot; &#39;{ print $2}&#39;)<br>    if [ -z &quot;$jdkbuildenv&quot; ]<br>    then<br>      jdkbuildenv=$(grep jvmTarget $varservice/build.gradle |head -n 1|awk -F &quot;=&quot; &#39;{ print $2}&#39;)<br>    fi<br><br>    jdkbuildenv=&quot;$(echo -e &quot;${jdkbuildenv}&quot; | tr -d &#39;[:space:]&#39;)&quot;<br>    <br>    case &quot;$jdkbuildenv&quot; in<br>    &quot;1.8&quot;|&quot;JavaVersion.VERSION_1_8&quot;|&quot;JvmTarget.JVM_1_8&quot;)<br>      export JAVA_HOME=$JDK8<br>      jdkbuildenv=&quot;jdk8&quot;<br>      ;;<br>    &quot;11&quot;|&quot;JavaVersion.VERSION_11&quot;|&quot;JvmTarget.JVM_11&quot;)<br>      export JAVA_HOME=$JDK11<br>      jdkbuildenv=&quot;jdk11&quot;<br>      ;;      <br>    &quot;17&quot;|&quot;JavaVersion.VERSION_17&quot;|&quot;JvmTarget.JVM_17&quot;)<br>      export JAVA_HOME=$JDK17<br>      jdkbuildenv=&quot;jdk17&quot;<br>      ;;<br>    *)<br>      echo &quot;Unknown JDK version! Specify 8,11,17!&quot;<br>      export JAVA_HOME=$JDK8<br>      jdkbuildenv=&quot;unknown,use jdk8&quot;<br>      ;;<br>    esac<br><br>    # has sub project<br>    subprj=$($varservice/gradlew -q -b $varservice/build.gradle projects&lt;/dev/null|grep &quot;Project &#39;:&quot;|head -n 1|cut -d &#39; &#39; -f 3)<br>    if [ -z &quot;$subprj&quot; ]    <br>    then<br>      springver=$($varservice/gradlew -b $varvalue/build.gradle -q dependencyInsight --dependency org.springframework.boot:spring-boot&lt;/dev/null|grep &quot;spring-boot&quot;|head -n 1 |awk -F&quot;:&quot; &#39;{ print $3}&#39;)||true<br>    else<br>      subprj=&quot;$(echo -e &quot;${subprj})&quot; | tr -d &quot;&#39;&quot;)&quot;<br>      springver=$($varservice/gradlew -b $varvalue/build.gradle -q $subprj:dependencyInsight --dependency org.springframework.boot:spring-boot&lt;/dev/null|grep &quot;spring-boot&quot;|head -n 1 |awk -F&quot;:&quot; &#39;{ print $3}&#39;)||true<br>    fi<br><br><br>  else<br>    echo &quot;not-gradle-project&quot;<br>    jdkbuildenv=&quot;unknown,use jdk8&quot;<br>    springver=&quot;not-gradle-project&quot;<br>  fi<br><br>  # connect POD to fetch JDK runtime env<br>  podname=$($kt get pods -o name -n $varservice|head -n 1)<br>  if [ -z &quot;$podname&quot; ]<br>  then<br>    echo &quot;No POD&quot;<br>    jdkver=&quot;No POD&quot;<br>  else<br>    jdkver=$($kt exec $podname -n $varservice -- java &quot;-version&quot; 2&gt;&amp;1|grep jdk)||true<br>  fi<br><br><br>  if [ -z &quot;$jdkver&quot; ]<br>  then<br>    echo &quot;use buildenv&quot;<br>    jdkver=&quot;unable to connect to POD, buildenv=$jdkbuildenv&quot;<br>  fi<br><br>  if [ -z &quot;$springver&quot; ]<br>  then    <br>    springver=&quot;unknown-spring&quot;<br>  fi<br><br><br>  echo &quot;$varenv:$varsquad:$varservice:$springver:$jdkbuildenv:$jdkver&quot;<br>  echo &quot;$varenv:$varsquad:$varservice:$springver:$jdkbuildenv:$jdkver&quot;&gt;&gt; $output <br>  <br>done &lt; $servicelist</pre><p>and this is my services.csv file</p><pre>cat services.csv <br>camel-spring-demo,team1,production</pre><p>Output is:</p><pre>cat spring-ver.csv # it is spring-boot 3.0.2<br>production:team1:camel-spring-demo:3.0.2 (selected by rule):jdk17:openjdk version &quot;17.0.7&quot; 2023-04-18 LTS</pre><p><strong><em>Happy Coding!</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4acd7ee4ef6" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>