DNSキャッシュ時間の問題

Cloud Native Build Pack で Spring Boot アプリのコンテナイメージを作れるから簡単だと思っていると、いい感じにDNSキャッシュ時間の設定値を構成する方法が無くて困る。

blog.astj.space

1. $JAVA_HOME/lib/security/java.security に記述

初期設定では、Cloud Natie Buildpack でイメージを作成すると、JREpaketo-buildpack/bellsoft-libericaを使う。

このBuildpackは、環境変数JAVA_TOOLS_OPTS-Djava.security.properties=<path>を埋め込むようになっており、java.securityの代わりに別の場所へ配置したプロパティファイルを使うようになる。

起動時のログでも確認できる。

Setting Active Processor Count to 8
Calculating JVM memory based on 2708640K available memory
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx2327887K -XX:MaxMetaspaceSize=73552K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 2708640K, Thread Count: 50, Loaded Class Count: 10572, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=8 -XX:MaxDirectMemorySize=10M -Xmx2327887K -XX:MaxMetaspaceSize=73552K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true
 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.5)

つまり、Buildpackの作成したレイヤーに作成されていることがわかる。

ビルド時に内容を設定できるのか考えたけど、ただ作ってるだけでそういう機能はないらしい。(paketo-buildpacks/libjvm/blob/main/java_security_properties.go#L47

まとめ

  • Cloud Native Buildpackで作成したコンテナイメージは、標準と違う場所に置いたプロパティファイルを使用する
  • コンテナイメージを作成するとき、設定だけでプロパティファイルをカスタマイズする方法は不明
  • コンテナイメージを実行するとき、設定だけでプロパティファイルをカスタマイズする方法は不明
  • コンテナイメージ実行時に、ボリュームマウントして任意のファイルを読ませることはできる

2. java.security.Security.setProperty() で指定

Spring Bootアプリはmainメソッドを作ることが多いと思うので、リンク先の記事のようにInetAddressCachePolicyの読み込み順を意識せずに対応できると思う。

毎回書かないで済むようBuildpackが対応して欲しい。

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    static {
        try {
            var cacheTTL = System.getProperty("networkaddress.cache.ttl", "1");
            var cacheNegativeTTL = System.getProperty("networkaddress.cache.negative.ttl", "1");
            java.security.Security.setProperty("networkaddress.cache.ttl", cacheTTL);
            java.security.Security.setProperty("networkaddress.cache.negative.ttl", cacheNegativeTTL);
        } catch (Exception ignore) {
            // XXX
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

A5:SQL Mk-2のER図(.a5er)からDDLを生成する

TL;DR

コマンドラインユーティリティ自体の実行方法

A5:SQL Mk-2 コマンドラインユーティリティからzipファイルをダウンロードして適当な場所に展開します。

zipファイルには説明書も付属してます。
ここの説明はそれを元にしてるので、正確な詳しい内容は説明書を参照してください

次のように実行すると、c:\work\model.a5er に置いたER図から c:\work\model.ddlSQLDDL)が生成されます。

A5M2cmd.exe /ERDDL ^
/ERD=c:\work\model.a5er ^
/OutFileName=c:\work\model.sql

Maven で実行する方法

A5:SQL Mk-2 で ER 図を開いていると、他のプロセスからファイルを開けなくて失敗します

MavenDDLを生成できるようになっていると便利な気がします。

mvn generate-sources -Pgenerate-sources-ddl -Derd.path=c:/work/model.a5er

こういうふうに記述すると実現できます。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>a5m2cmd-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>a5m2cmd-demo</name>
    <packaging>jar</packaging>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
    </dependencies>

    <profiles>
        <profile>
            <id>generate-sources-ddl</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <properties>
                <erd.path>c:/work/model.a5er</erd.path>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>wagon-maven-plugin</artifactId>
                        <version>2.0.2</version>
                        <executions>
                            <!-- A5m2cmd.zipをダウンロード -->
                            <execution>
                                <id>fetch-a5m2command</id>
                                <phase>generate-sources</phase>
                                <goals>
                                    <goal>download-single</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <skipIfExists>true</skipIfExists>
                            <url>https://ftp.vector.co.jp</url>
                            <fromFile>71/97/3301/A5M2cmd_2.14.3_x64.zip</fromFile>
                            <toFile>${project.build.directory}/A5M2cmd/A5M2cmd.zip</toFile>
                        </configuration>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-antrun-plugin</artifactId>
                        <version>1.8</version>
                        <executions>
                            <!-- A5m2cmd.zipをtargetに展開 -->
                            <execution>
                                <id>extract-zip</id>
                                <phase>generate-sources</phase>
                                <goals>
                                    <goal>run</goal>
                                </goals>
                                <configuration>
                                    <tasks>
                                        <unzip src="${project.build.directory}/A5M2cmd/A5M2cmd.zip" dest="${project.build.directory}/A5M2cmd" />
                                    </tasks>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>exec-maven-plugin</artifactId>
                        <version>3.0.0</version>
                        <executions>
                            <!-- A5m2cmd.exeを実行してDDLを生成 -->
                            <execution>
                                <id>generate-sources-ddl</id>
                                <phase>generate-sources</phase>
                                <goals>
                                    <goal>exec</goal>
                                </goals>
                                <configuration>
                                    <executable>${project.build.directory}/A5M2cmd/A5M2cmd.exe</executable>
                                    <arguments>
                                        <argument>/ERDDL</argument>
                                        <argument>/ERD=${erd.path}</argument>
                                        <argument>/OutEncoding=UTF-8</argument>
                                        <argument>/OutFileName=${project.basedir}/src/main/resources/db/migration/V0_0_001__table.sql</argument>
                                        <argument>/RDBMSType=POSTGRESQL</argument>
                                        <argument>/CreateOrder=Dependent</argument>
                                        <argument>/GenerateComment=Y</argument>
                                        <argument>/GenerateDropTableStatement=Y</argument>
                                        <argument>/DropTableIfExists=Y</argument>
                                        <argument>/BackupRestoreTempTable=N</argument>
                                        <argument>/ForceQuoteIdentifier=N</argument>
                                        <argument>/CreatePkIndex=Y</argument>
                                        <argument>/CreateFk=Y</argument>
                                        <argument>/CreateFK_ParentCard1Only=Y</argument>
                                        <argument>/FKParentIndex=Y</argument>
                                    </arguments>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

Gradle で実行する方法

こういう感じに実行できると便利そうです。

gradle generateDDL -Perd.path=c:/work/model.a5er

こういう風に記述すると実現できます。

plugins {
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

def erdPath = findProperty('erd.path') ?: 'er.a5er'

repositories {
    mavenCentral()
}

task fetchA5M2cmd {
    def url = 'https://ftp.vector.co.jp/71/97/3301/A5M2cmd_2.14.3_x64.zip'
    doLast {
        file("${buildDir}").mkdirs()
        def a5M2cmdZip = file("${buildDir}/A5M2cmd.zip")
        a5M2cmdZip.withOutputStream { os -> new URL(url).withInputStream { is -> os << is }}
    }
}

task unzipA5M2cmd(type: Copy) {

  def zipPath = file("${buildDir}/A5M2cmd.zip")
  def zipFile = file(zipPath)
  def outputDir = file("${buildDir}/A5M2cmd")

  from zipTree(zipFile)
  into outputDir
}

task generateDDL(type:Exec) {

    executable file("${buildDir}/A5M2cmd/A5M2cmd.exe")
    args = [
        "/ERDDL",
        "/ERD=${erdPath}",
        "/OutEncoding=UTF-8",
        "/OutFileName=${projectDir}/src/main/resources/db/migration/V0_0_001__table.sql",
        "/RDBMSType=POSTGRESQL",
        "/CreateOrder=Dependent",
        "/GenerateComment=Y",
        "/GenerateDropTableStatement=Y",
        "/DropTableIfExists=Y",
        "/BackupRestoreTempTable=N",
        "/ForceQuoteIdentifier=N",
        "/CreatePkIndex=Y",
        "/CreateFk=Y",
        "/CreateFK_ParentCard1Only=Y",
        "/FKParentIndex=Y",
    ]
}

tasks.generateDDL.dependsOn 'fetchA5M2cmd', 'unzipA5M2cmd'

おまけ:GitリポジトリでER図(.a5er)を管理するときは .gitattributes を作ったほうがいい

ER図(.a5er)ファイルの改行コードは CRLF ですが、ソースコードや設定ファイルの改行コードは LF になっている場合がほとんどです。

そういう状態のままER図を編集していると、コミット時に改行コードが変換されることを示す警告メッセージが表示されます。

改行コードの変換を避ける方法はいろいろあるのですが、 .gitattributes を設定するのがいいと思います。

Git - Git の属性

具体的にはリポジトリに .gitattributes というファイルを作成して次のように記述します。

*.a5er text eol=crlf

第4回 The Software Architect Elevator 読書会@リモート

javaee-study.connpass.com


参加者トピック

ディスカッション

Don't get locked up into avoiding lock-in

martinfowler.com

14. If Software Eats the World, Better Use Version Control!

15. A4 Paper Doesn’t Stifle Creativity

  • 標準化と差別化と
  • Wardley Maps
  • A4用紙という規格の話がアーキテクチャとどう関係するのかよく分からん
    • アーキテクチャのどこかの部分を標準化するとしたらの話
      • 「A4用紙」という標準を採用しても「絵を書きたい」「メモしたい」という創造性は阻害しない
      • IT標準を採用するとして「ビジネス価値」に注力できるようになっている?そうならないとダメじゃない?
  • こういう解釈もできそう
    • 部署によって同じ観点の表現に互換性がなく、本当はつながるはずのシステムがどのようにつながるのか分からない問題がある
    • それぞれを結びつける標準について言及しているんじゃないかと読み取った
  • 共通するのは「本質的でないことに煩わされないためにどうすればいいか」かなぁ

参考情報

JSONプロセッサーjq

使用頻度は高いのに使い方とかドキュメントをちゃんと読んでなかったので調査してきた。


趣旨

jqのマニュアルを読んだりしてるうちに日本語版ができたので個人サイトで見れるようにした。

jq マニュアル・チュートリアル(非公式訳)


経緯

ドキュメントは jinja2 でビルドしてるのが分かった。

ただ読むより翻訳したほうがいいかと思ったのでforkして対応してみた

結果は個人のgithub.ioで見えるようにした。

もちろん本体にも貢献したいんだけど、リポジトリがだいぶ前から停滞してるようなのでPRするのは諦めた。

github.com:stedolan/jqはissueとPRの数もやばい
issueとPRの数もやばい

テキストファイル処理の逆引き辞典的な何か

ファイル形式別にCLIツールと基本的な操作を整理したかった。

対象の形式とCLIツール

対象の操作

  • select/filter
  • update
  • delete

形式別のまとめ


plain text

Linuxコマンド逆引き大全 Index:テキスト操作にまとまっててよい。

// https://httpbin.org/json
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "title": "Wake up to WonderWidgets!",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Overview",
        "type": "all"
      }
    ],
    "title": "Sample Slide Show"
  }
}

select/filter

title"Wake up"含む 行を選択する。

$ cat httpbin.json | grep -o -P '"title": "Wake up.*"'
"title": "Wake up to WonderWidgets!"

title"Wake up"含まない 行を選択する。 (slideshow.slides[].title だけを抽出するのは難しい)

$ at httpbin.json | grep -o -P '"title": "(?!Wake up).*"'
"title": "Overview"
"title": "Sample Slide Show"

update

s コマンドで slideshow.title を上書きする。

\(\s\{4\}"title"\):\s\{0,1\}"[^"]*" の説明。

パターン 意味
\( 置換した後も残す部分のグループを開始
\s\{4\} 4文字の空白文字(U+0020)にマッチするパターン
"title" 見たままの文字列にマッチするパターン
\) 置換した後も残す部分のグループを終了
: 見たままの文字列にマッチするパターン
\s\{0,1\} 0文字以上1文字以下の空白文字(U+0020)にマッチするパターン
"[^"]*" ダブルクォート(U+0022)で囲まれた、ダブルクォート(U+0022)以外の文字列にマッチするパターン
 cat httpbin.json | sed 's/\(\s\{4\}"title"\):\s\{0,1\}"[^"]*"/\1: "Updated Slide Show"/1'
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "title": "Updated Slide Show",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Updated Slide Show",
        "type": "all"
      }
    ],
    "title": "Updated Slide Show"
  }
}

delete

d コマンドで slideshow.title を消す。

  • 前の行の末尾のカンマ(U+002c)も削除しないと構造が壊れてしまう
  • 複数行マッチ機能を使えばどうにかなるのかもしれない
cat httpbin.json | sed '/\s\{2\}"title":.*/d'
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "type": "all"
      }
    ],
  }
}

merge/override/append


JSON

jq Manualを読み解けばなんでも出来るようになる。

jq playで実験できる。

インストール

Linux/WSL

# Ubuntu/Debian
$ sudo apt-get install jq
# CentOS/RHEL
$ sudo yum install jq
# Binary
$ mkdir -p "${HOME}/bin"
$ curl -fsSL --output "${HOME}/bin/jq" "https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64"
$ chmod +x "${HOME}/bin/jq"

Windows

# Chocolatey
PS> chocolatey install jq
# Scoop
PS> scoop install jq
# Binary
PS> New-Item -Path $env:USERPROFILE -Name bin -ItemType Directory -Force
PS> curl -fsSL --output "$env:USERPROFILE/bin/jq.exe" "https://github.com/stedolan/jq/releases/download/jq-1.6/jq-win64.exe"
// https://httpbin.org/json
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "title": "Wake up to WonderWidgets!",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Overview",
        "type": "all"
      }
    ],
    "title": "Sample Slide Show"
  }
}

select/filter

select(boolean_expression)

title"Wake up"含む オブジェクトを選択する。

# slideshow.slides.title contains "Wake up"
$ cat httpbin.json | jq -r '.slideshow.slides[] | select(.title | contains("Wake up")) | .'
{
  "title": "Wake up to WonderWidgets!",
  "type": "all"
}

title"Wake up"含まない オブジェクトを選択する。

# slideshow.slides.title not contains "Wake up"
$ cat httpbin.json | jq -r '.slideshow.slides[] | select(.title | contains("Wake up") | not) | .'
{
  "items": [
    "Why <em>WonderWidgets</em> are great",
    "Who <em>buys</em> WonderWidgets"
  ],
  "title": "Overview",
  "type": "all"
}

update

Addition:+

+ は上書きする(もとの slideshow が上書きされた)。

$ cat httpbin.json | jq -r '. + {slideshow: {title: "Updated Slide Show"}}'
{
  "slideshow": {
    "title": "Updated Slide Show"
  }
}

Multiplication, division, modulo: *, /, and %

* はマージする(もとの slideshow に新しい slideshow がマージされた)。

$ cat httpbin.json | jq -r '. * {slideshow: {title: "Updated Slide Show"}}'
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "title": "Wake up to WonderWidgets!",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Overview",
        "type": "all"
      }
    ],
    "title": "Updated Slide Show"
  }
}

delete

演算子はないから代わりに新しいオブジェクトを作る。(元のオブジェクトから author を消した)

$ cat httpbin.json | \
jq -r '.slideshow | {slideshow: { date: .date, title: .title, slides: .slides }}'
{
  "slideshow": {
    "date": "date of publication",
    "title": "Sample Slide Show",
    "slides": [
      {
        "title": "Wake up to WonderWidgets!",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Overview",
        "type": "all"
      }
    ]
  }
}

YAML

インストール

Linux/WSL

# Binary
$ mkdir -p "${HOME}/bin"
$ durl=$(curl -fsSL "https://api.github.com/repos/mikefarah/yq/releases/latest" | \
jq -r '.assets[] | select( .name | contains("linux_amd64")) | .browser_download_url')
$ curl -fsSL --output "${HOME}/bin/yq" "${durl}"
$ chmod +x "${HOME}/bin/yq"

Windows

# Chocolatey
PS> chocolatey install yq
# Scoop
PS> scoop install yq
# Binary
PS> New-Item -Path $env:USERPROFILE -Name bin -ItemType Directory -Force
PS> $durl = curl -fsSL "https://api.github.com/repos/mikefarah/yq/releases/latest" | `
ConvertFrom-Json | `
Select -Expand assets | `
Where -Match -Property name -Value windows_amd64.exe | `
Select -ExpandProperty browser_download_url
PS> curl -fsSL --output "$env:USERPROFILE/bin/yq.exe" $durl
# httpbin.yaml
slideshow:
  author: Yours Truly
  date: date of publication
  slides:
    - title: Wake up to WonderWidgets!
      type: all
    - items:
        - Why <em>WonderWidgets</em> are great
        - Who <em>buys</em> WonderWidgets
      title: Overview
      type: all
  title: Sample Slide Show

select/filter

Select - yq

title"Wake up to WonderWidgets!"一致する オブジェクトを選択する。

$ yq e '.slideshow.slides[] | select(.title == "Wake up to WonderWidgets!") | .' httpbin.yaml
title: Wake up to WonderWidgets!
type: all

title"Wake up to WonderWidgets!"一致しない オブジェクトを選択する。

$ yq e '.slideshow.slides[] | select(.title != "Wake up to WonderWidgets!") | .' httpbin.yaml
items:
  - Why <em>WonderWidgets</em> are great
  - Who <em>buys</em> WonderWidgets
title: Overview
type: all

update

Reduce - yq

# other.yaml
slideshow:
  title: Updated

同じ構造のドキュメントをマージする。

$ yq eval-all '. as $item ireduce ({}; . * $item)' httpbin.yaml other.yaml
slideshow:
  author: Yours Truly
  date: date of publication
  slides:
    - title: Wake up to WonderWidgets!
      type: all
    - items:
        - Why <em>WonderWidgets</em> are great
        - Who <em>buys</em> WonderWidgets
      title: Overview
      type: all
  title: Updated

delete

Delete - yq

slideshow.author を削除する。

$ yq e 'del(.slideshow.author)' httpbin.yaml
slideshow:
  date: date of publication
  slides:
    - title: Wake up to WonderWidgets!
      type: all
    - items:
        - Why <em>WonderWidgets</em> are great
        - Who <em>buys</em> WonderWidgets
      title: Overview
      type: all
  title: Sample Slide Show

XML

インストール

Linux/WSL

# Ubuntu/Debian
$ apt-get install xmlstarlet
# CentOS/RHEL
$ yum install xmlstarlet

Windows

# Chocolatey
PS> chocolatey install xmlstarlet
# Scoop
PS> scoop install xmlstarlet
<?xml version='1.0' encoding='us-ascii'?>

<!--  A SAMPLE set of slides  -->

<slideshow
    title="Sample Slide Show"
    date="Date of publication"
    author="Yours Truly"
    >

    <!-- TITLE SLIDE -->
    <slide type="all">
      <title>Wake up to WonderWidgets!</title>
    </slide>

    <!-- OVERVIEW -->
    <slide type="all">
        <title>Overview</title>
        <item>Why &gt;em&lt;WonderWidgets&gt;/em&lt; are great</item>
        <item/>
        <item>Who &gt;em&lt;buys&gt;/em&lt; WonderWidgets</item>
    </slide>

</slideshow>

select/filter

1. Querying XML documents

title"Wake up"含む オブジェクトを選択する。

$ cat httpbin.xml | MSYS_NO_PATHCONV=1 xml sel -t -m '/slideshow/slide/title[contains(., "Wake up")]' -c ..
<slide type="all">
      <title>Wake up to WonderWidgets!</title>
    </slide>

title"Wake up"含まない オブジェクトを選択する。

$ cat httpbin.xml | MSYS_NO_PATHCONV=1 xml sel -t -m '/slideshow/slide/title[not(contains(., "Wake up"))]' -c ..
<slide type="all">
        <title>Overview</title>
        <item>Why &gt;em&lt;WonderWidgets&gt;/em&lt; are great</item>
        <item/>
        <item>Who &gt;em&lt;buys&gt;/em&lt; WonderWidgets</item>
    </slide>

update

3. Editing XML documents

slideshow[@title] を更新する。

$ cat httpbin.xml | MSYS_NO_PATHCONV=1 xml ed -u '/slideshow/@title' -v "Updated"
<?xml version="1.0" encoding="us-ascii"?>
<!--  A SAMPLE set of slides  -->
<slideshow title="Updated" date="Date of publication" author="Yours Truly">
  <!-- TITLE SLIDE -->
  <slide type="all">
    <title>Wake up to WonderWidgets!</title>
  </slide>
  <!-- OVERVIEW -->
  <slide type="all">
    <title>Overview</title>
    <item>Why &gt;em&lt;WonderWidgets&gt;/em&lt; are great</item>
    <item/>
    <item>Who &gt;em&lt;buys&gt;/em&lt; WonderWidgets</item>
  </slide>
</slideshow>

delete

3. Editing XML documents

slideshow[@author] を削除する。

$ cat httpbin.xml | MSYS_NO_PATHCONV=1 xml ed -d '/slideshow/@author'
<?xml version="1.0" encoding="us-ascii"?>
<!--  A SAMPLE set of slides  -->
<slideshow title="Sample Slide Show" date="Date of publication">
  <!-- TITLE SLIDE -->
  <slide type="all">
    <title>Wake up to WonderWidgets!</title>
  </slide>
  <!-- OVERVIEW -->
  <slide type="all">
    <title>Overview</title>
    <item>Why &gt;em&lt;WonderWidgets&gt;/em&lt; are great</item>
    <item/>
    <item>Who &gt;em&lt;buys&gt;/em&lt; WonderWidgets</item>
  </slide>
</slideshow>

TOML

インストール

Linux(WSL)

# Python
$ pip install wildq
# Ubuntu/Debian
$ durl=$(curl -fsSL "https://api.github.com/repos/ahmet2mir/wildq/releases/latest" | \
jq -r '.assets[] | select( .name | contains(".deb")) | .browser_download_url')
$ curl -fsSL --output "${HOME}/wildq.deb" "${durl}"
$ sudo dpkg -i "${HOME}/wildq.deb"
# CentOS/RHEL
$ durl=$(curl -fsSL "https://api.github.com/repos/ahmet2mir/wildq/releases/latest" | \
jq -r '.assets[] | select( .name | contains(".rpm")) | .browser_download_url')
$ curl -fsSL --output "${HOME}/wildq.rpm" "${durl}"
$ sudo yum install -y "${HOME}/wildq.rpm"
# Binary
$ mkdir -p "${HOME}/bin"
$ durl=$(curl -fsSL "https://api.github.com/repos/ahmet2mir/wildq/releases/latest" | \
jq -r '.assets[] | select( .name | contains("linux-x86_64.tar.gz")) | .browser_download_url')
$ curl -fsSL "${durl}" | tar -xzvf - -C "${HOME}/bin" wq wildq

Windows

# Python
PS> pip install wildq
# httpbin.toml
[slideshow]
author = "Yours Truly"
date = "date of publication"
title = "Sample Slide Show"

[[slideshow.slides]]
title = "Wake up to WonderWidgets!"
type = "all"

[[slideshow.slides]]
items = ["Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets"]
title = "Overview"
type = "all"

select/filter

title"Wake up"含む オブジェクトを選択する。

$ cat httpbin.toml | wildq -i toml -r '.slideshow.slides[] | select(.title | contains("Wake up")) | .'
title = "Wake up to WonderWidgets!"
type = "all"

title"Wake up"含まない オブジェクトを選択する。

$ cat httpbin.toml | wildq -i toml -r '.slideshow.slides[] | select(.title | contains("Wake up") | not) | .'
items = [ "Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets",]
title = "Overview"
type = "all"

update

+ は上書きする(もとの slideshow が上書きされた)。

$ $ cat httpbin.toml | wildq -i toml -r '. + {slideshow: {title: "Updated Slide Show"}}'
[slideshow]
title = "Updated Slide Show"

* はマージする(もとの slideshow に新しい slideshow がマージされた)。

$ cat httpbin.toml | wildq -i toml -r '. * {slideshow: {title: "Updated Slide Show"}}'
[slideshow]
author = "Yours Truly"
date = "date of publication"
title = "Updated Slide Show"
[[slideshow.slides]]
title = "Wake up to WonderWidgets!"
type = "all"

[[slideshow.slides]]
items = [ "Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets",]
title = "Overview"
type = "all"

delete

演算子はないから代わりに新しいオブジェクトを作る。(元のオブジェクトから author を消した)

$ cat httpbin.toml | \
wildq -i toml -r '.slideshow | {slideshow: { date: .date, title: .title, slides: .slides }}'
[slideshow]
date = "date of publication"
title = "Sample Slide Show"
[[slideshow.slides]]
title = "Wake up to WonderWidgets!"
type = "all"

[[slideshow.slides]]
items = [ "Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets",]
title = "Overview"
type = "all"

CSV

インストール

Linux/WSL

# Ubuntu/Debian
$ durl=$(curl -fsSL "https://api.github.com/repos/harelba/q/releases/latest" | \
jq -r '.assets[] | select( .name | contains(".deb")) | .browser_download_url')
$ curl -fsSL --output "${HOME}/q.deb" "${durl}"
$ sudo dpkg -i "${HOME}/q.deb"
# CentOS/RHEL
$ durl=$(curl -fsSL "https://api.github.com/repos/harelba/q/releases/latest" | \
jq -r '.assets[] | select( .name | contains(".rpm")) | .browser_download_url')
$ curl -fsSL --output "${HOME}/q.rpm" "${durl}"
$ sudo yum install -y "${HOME}/q.rpm"
# Binary
$ mkdir -p "${HOME}/bin"
$ durl=$(curl -fsSL "https://api.github.com/repos/harelba/q/releases/latest" | \
jq -r '.assets[] | select( .name | contains("-Linux")) | .browser_download_url')
$ curl -fsSL --output "${HOME}/bin/q" "${durl}"
$ chmod +x "${HOME}/bin/q"

Windows

# Binary
PS> New-Item -Path $env:USERPROFILE -Name bin -ItemType Directory -Force
PS> $durl = curl -fsSL "https://api.github.com/repos/harelba/q/releases/latest" | `
ConvertFrom-Json | `
Select -Expand assets | `
Where -Match -Property name -Value Windows.exe | `
Select -ExpandProperty browser_download_url
PS> curl -fsSL --output "$env:USERPROFILE/bin/q.exe" $durl

github.com/inouet/ken-all/testdata/test_001.csv

注意: 文字コードSJISからUTF-8へ、改行コードをLFへ変換すること

PS> (curl -fsSL https://raw.githubusercontent.com/inouet/ken-all/master/testdata/test_001.csv) -join "`n" | Set-Content -Path test_001.csv -Encoding utf8

PS> q -b -O -d , 'SELECT * FROM test_001.csv'
c1  ,c2,c3    ,c4     ,c5          ,c6                ,c7 ,c8    ,c9         ,c10,c11,c12,c13,c14,c15
1101,60,600000,ホッカイドウ,サッポロシチュウオウク,イカニケイサイガナイバアイ   ,北海道,札幌市中央区,以下に掲載がない場合 ,0  ,0  ,0  ,0  ,0  ,0
1101,64,640941,ホッカイドウ,サッポロシチュウオウク,アサヒガオカ           ,北海道,札幌市中央区,旭ケ丘        ,0  ,0  ,1  ,0  ,0  ,0
1101,60,600041,ホッカイドウ,サッポロシチュウオウク,オオドオリヒガシ        ,北海道,札幌市中央区,大通東        ,0  ,0  ,1  ,0  ,0  ,0
1101,60,600042,ホッカイドウ,サッポロシチュウオウク,オオドオリニシ(1-19チョウメ),北海道,札幌市中央区,大通西(1〜19丁目),1  ,0  ,1  ,0  ,0  ,0

select/filter

旭ケ丘含む 行を選択する。

$ q -b -O -d , 'SELECT * FROM test_001.csv WHERE c9="旭ケ丘"'
c1  ,c2,c3    ,c4     ,c5          ,c6     ,c7 ,c8    ,c9 ,c10,c11,c12,c13,c14,c15
1101,64,640941,ホッカイドウ,サッポロシチュウオウク,アサヒガオカ,北海道,札幌市中央区,旭ケ丘,0  ,0  ,1  ,0  ,0  ,0

旭ケ丘含まない 行を選択する。

$ q -b -O -d , 'SELECT * FROM test_001.csv WHERE c9<>"旭ケ丘"'
c1  ,c2,c3    ,c4     ,c5          ,c6                ,c7 ,c8    ,c9         ,c10,c11,c12,c13,c14,c15
1101,60,600000,ホッカイドウ,サッポロシチュウオウク,イカニケイサイガナイバアイ   ,北海道,札幌市中央区,以下に掲載がない場合 ,0  ,0  ,0  ,0  ,0  ,0
1101,60,600041,ホッカイドウ,サッポロシチュウオウク,オオドオリヒガシ        ,北海道,札幌市中央区,大通東        ,0  ,0  ,1  ,0  ,0  ,0
1101,60,600042,ホッカイドウ,サッポロシチュウオウク,オオドオリニシ(1-19チョウメ),北海道,札幌市中央区,大通西(1〜19丁目),1  ,0  ,1  ,0  ,0  ,0

update

plain text の項を参照。

delete

plain text の項を参照。


TSV

インストール

CSV の項を参照。

github.com/harelba/q/examples/exampledatafile

PS> curl -fsSL --remote-name https://raw.githubusercontent.com/harelba/q/master/examples/exampledatafile

PS> q "SELECT * FROM exampledatafile LIMIT 10"
-rw-r--r-- 1 root root 2064 2006-11-23 21:33 netscsid.conf
-rw-r--r-- 1 root root 1343 2007-01-09 20:39 wodim.conf
-rw-r--r-- 1 root root 112 2007-06-22 18:08 apg.conf
-rw-r--r-- 1 root root 15752 2009-07-25 18:13 ltrace.conf
-rw-r--r-- 1 root root 624 2010-05-16 14:18 mtools.conf
-rw-r--r-- 1 root root 395 2010-06-20 11:11 anacrontab
-rw-r--r-- 1 root root 18673 2010-10-18 06:49 globash.rc
-rw-r--r-- 1 root root 23958 2010-11-15 10:07 mime.types
-rw-r--r-- 1 root root 449 2010-11-15 10:07 mailcap.order
-rw-r--r-- 1 root root 8453 2010-12-03 22:32 nanorc

select/filter

ssh含む 行を選択する。

$ q 'SELECT * FROM exampledatafile WHERE c8="ssh"'
drwxr-xr-x 2 root root 4096 2011-12-22 18:57 ssh

ssh含まない 行を選択する。

$ q 'SELECT * FROM exampledatafile WHERE c8<>"ssh" ORDER BY c8 LIMIT 10'
drwxr-xr-x 5 root root 4096 2011-10-12 16:28 ConsoleKit
drwxr-xr-x 5 root root 4096 2011-10-12 16:30 NetworkManager
drwxr-xr-x 2 root root 4096 2011-03-15 23:05 ODBCDataSources
drwxr-xr-x 3 root root 4096 2011-12-18 12:09 OpenCL
drwxr-xr-x 2 root root 4096 2012-01-13 12:48 R
drwxr-xr-x 2 root root 4096 2011-10-12 16:30 UPower
drwxr-xr-x 10 root root 4096 2012-01-16 16:08 X11
drwxr-xr-x 3 root root 4096 2011-10-12 16:30 acpi
-rw-r--r-- 1 root root 2981 2011-10-12 16:27 adduser.conf
-rw-r--r-- 1 root root 10 2011-12-18 11:45 adjtime

update

plain text の項を参照。

delete

plain text の項を参照。


LTSV

インストール

Linux/WSL

# Binary
$ mkdir -p "${HOME}/bin"
$ durl=$(curl -fsSL "https://api.github.com/repos/sters/ltsvq/releases/latest" | \
jq -r '.assets[] | select( .name | contains("linux_amd64.tar.gz")) | .browser_download_url')
$ curl -fsSL "${durl}" | tar -xzvf - -C "${HOME}/bin" ltsvq

Windows

# Binary
# PS> New-Item -Path $env:USERPROFILE -Name bin -ItemType Directory -Force
# PS> $durl = curl -fsSL "https://api.github.com/repos/sters/ltsvq/releases/latest" | `
# ConvertFrom-Json | `
# Select -Expand assets | `
# Where -Match -Property name -Value windows_amd64.tar.gz | `
# Select -ExpandProperty browser_download_url
# PS> curl -fsSL --output "$env:USERPROFILE/ltsvq.tar.gz" $durl
# PS> tar -xzvf "$env:USERPROFILE/ltsvq.tar.gz" -C "$env:USERPROFILE/bin" ltsvq.exe

github.com/sters/ltsvq/example.ltsv

host:127.0.0.1    ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:192.168.1.1    ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:127.0.0.1  ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:192.168.1.1    ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:127.0.0.1  ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:192.168.1.1    ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:127.0.0.1  ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)
host:192.168.1.1    ident:- user:frank  time:[10/Oct/2000:13:55:36 -0700]   req:GET /apache_pb.gif HTTP/1.0 status:200  size:2326   referer:http://www.example.com/start.html   ua:Mozilla/4.08 [en] (Win98; I ;Nav)

select/filter

host の値が 192.始まる 行を選択する。

$ cat example.ltsv | ltsvq -q "select * from ltsv where host like '192%'"
_dummy: host:192.168.1.1        ident:- referer:http://www.example.com/start.html       req:GET /apache_pb.gif HTTP/1.0 size:2326       status:200      time:[10/Oct/2000:13:55:36 -0700]       ua:Mozilla/4.08 [en] (Win98; I ;Nav)     user:frank
_dummy: host:192.168.1.1        ident:- referer:http://www.example.com/start.html       req:GET /apache_pb.gif HTTP/1.0 size:2326       status:200      time:[10/Oct/2000:13:55:36 -0700]       ua:Mozilla/4.08 [en] (Win98; I ;Nav)     user:frank
_dummy: host:192.168.1.1        ident:- referer:http://www.example.com/start.html       req:GET /apache_pb.gif HTTP/1.0 size:2326       status:200      time:[10/Oct/2000:13:55:36 -0700]       ua:Mozilla/4.08 [en] (Win98; I ;Nav)     user:frank
_dummy: host:192.168.1.1        ident:- referer:http://www.example.com/start.html       req:GET /apache_pb.gif HTTP/1.0 size:2326       status:200      time:[10/Oct/2000:13:55:36 -0700]       ua:Mozilla/4.08 [en] (Win98; I ;Nav)     user:frank

host の値が 192.始まらない 行を選択する。

$ cat example.ltsv | ltsvq -q "select * from ltsv where host not like '192%'"
_dummy: host:127.0.0.1  ident:- referer:http://www.example.com/start.html   req:GET /apache_pb.gif HTTP/1.0 size:2326   status:200  time:[10/Oct/2000:13:55:36 -0700]   ua:Mozilla/4.08 [en] (Win98; I ;Nav)    user:frank  
_dummy: host:127.0.0.1  ident:- referer:http://www.example.com/start.html   req:GET /apache_pb.gif HTTP/1.0 size:2326   status:200  time:[10/Oct/2000:13:55:36 -0700]   ua:Mozilla/4.08 [en] (Win98; I ;Nav)    user:frank  
_dummy: host:127.0.0.1  ident:- referer:http://www.example.com/start.html   req:GET /apache_pb.gif HTTP/1.0 size:2326   status:200  time:[10/Oct/2000:13:55:36 -0700]   ua:Mozilla/4.08 [en] (Win98; I ;Nav)    user:frank  
_dummy: host:127.0.0.1  ident:- referer:http://www.example.com/start.html   req:GET /apache_pb.gif HTTP/1.0 size:2326   status:200  time:[10/Oct/2000:13:55:36 -0700]   ua:Mozilla/4.08 [en] (Win98; I ;Nav)    user:frank  

update

plain text の項を参照。

delete

plain text の項を参照。


properties

TBD

JaSST'21 Tokyoで視聴したセッションのメモ

JaSST'21 Tokyoで視聴したセッションのメモ。

イベント情報

ソフトウェアテストシンポジウム 2021 東京

セッション情報

基調講演 "Being Agile about Architecture"(Joseph W. Yoder (Refactory CEO))

Being Agile about Architecture

CI/CDパイプラインにおけるテストレベル(武井 眞人(テクバン))

後工程を最適化するために既存の仕組みを大きく変更するのはコストがかかるため、できるだけ負担が生じないように後工程の役割や事前条件を前工程に移していくのが実践知なんだなと思った。

スポンサーセッションだからか資料は見つからなかった

自動テスト普及活動の勘所(松⽊ 直之(富⼠通))

  • 概要:自動テストの普及にはとても地道な努力が必要
  • 紹介されてたこと
    • たくさんの「〇〇ができない」に応えるため自作したテストツールを配布している
    • 間口を広げる「入門」や活用を進める「事例紹介」の両方のセミナーしてる
    • 見積もりやPoCや技術選定のための基礎資料をメンテしてる

自動テストのコスト削減に対する効果は薄いから、その線で売り込むのは筋が悪いという話だった。 ただ、手動テストで不可能な網羅テストを実施できるということは、実質的にコスト削減できているということにもなるよね、という話もあってそれは確かにそうだなぁと思った。

スポンサーセッションだからか資料は見つからなかった

仕様整理のためのテスト設計入門(河野 哲也(DeNA))

  • テスト設計技法の紹介とワークのセッション
  • 名前は知らなくても自然とやっていたものがあるかも

普段の仕事にすぐ取り入れられる内容だった。 日本語であの場合この場合と議論するより、はるかに効率的で正確な仕様や設計を決められるので、是非とも習得してもらいたいテクニックだった。 別の表現に変換しなければテストできない仕様や設計は未完成なんですよ(主観)。

仕様整理のためのテスト設計入門

招待講演 "パターンQA2AQによるアジャイル品質のマインド、体制、プロセス、技術"(鷲崎 弘宜 氏(早稲田大学 / NII / システム情報 / エクスモーション))

  • スクラムによるソフトウェア開発に品質の観点を織り込もうとしている活動の紹介
    • パタンランゲージとアジャイルソフトウェア開発の理解が求められるので難しい……
  • 誰かがコメントしてたけど「スクラムマスターと同じように品質マスターが増えるような感じ」
    • つまり品質を保証するための型を作り、守・破・離していく

パターンQA2AQによるアジャイル品質のマインド、体制、プロセス、技術

QuarkusのSpring Integrationとネイティブ実行可能ファイル作成とコンテナイメージ作成を実験

QuarkusプロジェクトにSpring Boot アプリの実装を取り込んだデモプロジェクトでいろいろ実験してみた。

ソースコード一式のzipアーカイブ

QuarkusのSpringインテグレーション機能

Quarkus - Quarkus Extension for Spring DI API

この辺を注意するといいのかもしれません。

インテグレーション機能と考えるより、移行機能と考えるのがいいかもしれない。

  • エントリポイントは不要
    • @SpringBootApplicationのクラスは削除する
  • アプリケーションコンテキストの代替品はありません
    • おそらくCDIのコンテキストへ読み替えて実現しているため
    • したがって Spring Test で実装したテストコードは移行できない……(ちゃんと調べてないから分からないけど)
  • Spring Frameworkのライブラリに対応するQuarkusプラグインが、それぞれのアノテーションをQuarkusのコンテキストへ変換している

プロジェクトの生成

Mavenコマンドで生成します。

mvn io.quarkus:quarkus-maven-plugin:1.12.2.Final:create \
    -DprojectGroupId=com.example \
    -DprojectArtifactId=spring-data-jpa-web-security-demo \
    -DclassName="com.example.demo.GreetingController" \
    -Dpath="/greeting" \
    -Dextensions="resteasy,spring-web,spring-data-jpa,spring-security,quarkus-elytron-security-properties-file,resteasy-jackson,quarkus-jdbc-h2"

ネイティブ実行可能ファイルの作成

Quarkus - Building a Native Executable

nativeプロファイルでpackageフェーズを実行するとネイティブの実行可能ファイルを作成します。

実行可能ファイルはtarget/spring-data-jpa-web-security-demo-1.0.0-SNAPSHOT-runnerに作成します。

AOTコンパイルに時間がかかるのは変わらないのですが、フレームワーク自体がSpring Bootに比べて小さいため、ビルドマシンに要求するリソースは比較的低くなります。

./mvnw package -Pnative

コンテナイメージの作成

Quarkus - Container Images

コンテナイメージの作成方法が2種類あります。

  • Javaアプリケーションとしてコンテナイメージを作成
  • ネイティブ実行可能ファイルからコンテナイメージを作成

Javaアプリケーションとしてコンテナイメージを作成

Jibでコンテナイメージを作成するときはプロファイルにbuild-java-jibを指定します。

mvn package -Pbuild-java-jib

src/main/docker/Dockerfile.jvmに従ってコンテナイメージを作成するときはプロファイルにbuild-java-dockerを指定します。

mvn package -Pbuild-java-docker

作成したイメージの大きさはこれぐらいになります。ベースイメージが UBI になるため、比較的大きめ。

$ docker images com.example/spring-data-jpa-web-security-demo
REPOSITORY                                      TAG                 IMAGE ID       CREATED          SIZE
com.example/spring-data-jpa-web-security-demo   java-docker         93303798e24f   14 minutes ago   406MB
com.example/spring-data-jpa-web-security-demo   java-jib            bb8fd47095f3   15 minutes ago   225MB

ネイティブ実行可能ファイルからコンテナイメージを作成

Jibでコンテナイメージを作成するときはプロファイルにnative,build-navive-jibを指定します。

mvn package -Pnative,build-navive-jib

src/main/docker/Dockerfile.nativeに従ってコンテナイメージを作成するときはプロファイルにnative,build-navive-dockerを指定します。

mvn package -Pnative,build-navive-docker

src/main/docker/Dockerfile.native-distrolessに従ってコンテナイメージを作成するときはプロファイルにnative,build-navive-distrolessを指定します。

mvn package -Pnative,build-navive-distroless

作成したイメージの大きさはこれぐらいになります。 QuarkusのJibプラグインはベースイメージとしてregistry.access.redhat.com/ubi8/ubi-minimal(103MB)を利用するため、Dockerfileでdistrolessを使う場合に比べると少し大きくなってしまうのが分かります。

$ docker images com.example/spring-data-jpa-web-security-demo
REPOSITORY                                      TAG                 IMAGE ID       CREATED          SIZE
com.example/spring-data-jpa-web-security-demo   native-distroless   618ac1277742   7 seconds ago    87.3MB
com.example/spring-data-jpa-web-security-demo   native-docker       eae04a98e2d0   4 minutes ago    171MB
com.example/spring-data-jpa-web-security-demo   native-jib          232110e84e33   9 minutes ago    171MB

動作確認してみる。だいたいよさそうだ。

$ docker run --rm -i -p 8080:8080 com.example/spring-data-jpa-web-security-demo:java-jib
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-03-15 08:18:09,374 INFO  [io.agr.pool] (main) Datasource '<default>': Initial size smaller than min. Connections will be created when necessary
2021-03-15 08:18:10,221 INFO  [io.quarkus] (main) spring-data-jpa-web-security-demo 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.12.2.Final) started in 2.235s. Listening on: http://0.0.0.0:8080
2021-03-15 08:18:10,222 INFO  [io.quarkus] (main) Profile prod activated.
2021-03-15 08:18:10,222 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-h2, mutiny, narayana-jta, resteasy, resteasy-jackson, security, security-properties-file, smallrye-context-propagation, spring-data-jpa, spring-di, spring-security, spring-web]

---

$ curl --user test:test -s localhost:8080/books; echo
[]

$ curl --user test:test --request POST --header 'Content-Type: application/json' -d '{"title":"test 001"}' -s localhost:8080/book; echo
{"id":1,"title":"test 001"}

$ curl --user test:test --request POST --header 'Content-Type: application/json' -d '{"title":"test 002"}' -s localhost:8080/book; echo
{"id":2,"title":"test 002"}

$ curl --user test:test -s localhost:8080/books; echo
[{"id":1,"title":"test 001"},{"id":2,"title":"test 002"}]

$ curl --user test:test --request PUT --header 'Content-Type: application/json' -d '{"title":"test 001u1"}' -s localhost:8080/book?id=1; echo
{"id":1,"title":"test 001u1"}

$ curl --user test:test --request DELETE -s localhost:8080/book?id=2; echo
2

$ curl --user test:test -s localhost:8080/books; echo
[{"id":1,"title":"test 001u1"}]

$ curl -s localhost:8080/books; echo