如果要說明使用Podman Quadlet的最大好處作為說服理由,我一定選.container裡的Environment變數。它不僅可以替代運行環境時由Shell載入profile檔的環境變數,連JVM的環境變數以及Spring Boot的application.yaml或application.properties的環境變數也都取代。
這好處在哪?比如application.yaml在開發環境的DB連線資訊,可以不用為了SIT或UAT環境,指定profile重包一版image到SIT或UAT環境,只需在.container設定Environment變數替代application.yaml的DB連線資訊,image根本不用指定profile來重包,這是其一。
其二是若有些環境變數是在SIT與UAT才會用到,但開發環境不會用到,Environment變數只需要在SIT/UAT環境設定就行。所以這篇主旨是講.container的Environment怎麼架空profile。
在a2a專案,應客戶要求,DB、FTP的密碼要存在RedHat主機的/a2a/AP下的某密碼檔,該密碼檔與解密用的jar檔是客戶提供的,公司的開發環境是可以比照建置/a2a/AP子目錄及密碼檔,但本機開發的工程師大多是Windows及Mac,寫好程式不能在本機直接測終究是個麻煩,Podman Quadlet的.container的Environment的設計根本可以讓工程師忘了密碼檔的存在。
第一個要解決的問題是rootless帳號存取根目錄開始的子目錄讀取權限,也就是/a2a,所以在第六天的文章有講解法就是為了可以讓spring boot順利讀取。
在spring boot的application.yaml原本DB連線設定如下:
spring:
datasource:
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://10.1.2.5:1433;DatabaseName=A2A;SelectMethod=cursor;sendStringParametersAsUnicode=true;encrypt=false;trustServerCertificate=true
username: scott
password: tigher
hikari:
maxinum-pool-size: 10
connection-timeout: 0
若想直接改密碼值,直接在.container設定下的[Container]標籤加Environment就可以覆蓋:
[Container]
Environment=spring.datasource.password=newpassword
當然連DB的URL、帳號等參數也能這樣狸貓換太子、李代桃僵、抓交替、奪舍...。
前面說到客戶要求是用他們提供的jar去讀/a2a/AP下的密碼檔,所以等於Spring Boot啟動後,在載入application.yaml參數就要做解出密碼的動作,所以新增一支程式如下:
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import customer.pub.UserInfo; // 客戶提供的解密元件
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class A2AEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
boolean enabled = environment.getProperty(
"a2a.dynamic-password.enabled",
Boolean.class,
false
);
if (!enabled) {
return;
}
String datasourceUsername = environment.getProperty("spring.datasource.username");
String hostname = environment.getProperty("a2a.db.hostname");
if (datasourceUsername == null || datasourceUsername.trim().isEmpty()) {
throw new IllegalStateException("spring.datasource.username 未設定");
}
if (hostname == null || hostname.trim().isEmpty()) {
throw new IllegalStateException("a2a.db.hostname 未設定");
}
try {
String password = new UserInfo().GetUserValue(hostname, datasourceUsername);
if (password == null || password.trim().isEmpty()) {
throw new IllegalStateException("由 UserInfo 取得的 DB 密碼為空值");
}
Map<String, Object> map = new HashMap<>();
map.put("spring.datasource.password", password);
environment.getPropertySources().addFirst(new MapPropertySource("customerDbPassword", map));
} catch (Exception e) {
throw new IllegalStateException("透過 UserInfo 取得 DB 密碼失敗", e);
}
}
}
[Container]
Environment=a2a.db.hostname=10.1.2.5
Environment=a2a.dynamic-password.enabled=true