Jenkins による リリース & Git操作 & build 入門

Jenkins による リリース & Git操作 & build 入門:


Jenkins による リリース & Git操作 & build 入門


68747470733a2f2f73656b69796165696a692e67




00. Jenkinsについて



Jenkinsとは



準備された仕事を指定されたタイミングで実行する箱

  • 継続的インテグレーション(continuous integration)ツール
  • アジャイル型開発での利用を前提に設計された
  • on-premises(自社運用)型のため企業において一括管理しやすい
  • 日本製で、作者は川口耕介氏
  • "ジョブ"と呼ばれる用途単位の機能を順次実行していくことでGit操作ファイル転送を実行する
  • 1つのジョブは、対象Gitブランチ設定やGitコマンド、シェルコマンドなどを設定することで1つの機能を担う構造になっている
  • ジョブを一度作成すると実行時に機能の詳細内容を意識することなく反復利用できる
  • 初期状態では、ジョブが何も与えられてない"からっぽのハコ"のようなもの


川口耕介氏



68747470733a2f2f73656b69796165696a692e67




なぜCIツール

  • 機械が得意なことを機械に任せる

    • 差分抽出
    • 反復作業
    • 転送設定
    • 高速リリース
    • テスト
  • ローカルルールの撲滅

    • 本来創意工夫の必要のない作業
    • 属人的な要素の排除


68747470733a2f2f73656b69796165696a692e67




その他のCIサービス



68747470733a2f2f73656b69796165696a692e67




68747470733a2f2f73656b69796165696a692e67




01. シェルコマンド


Jenkins の Job でよく利用する シェルコマンド を紹介



Jenkins 「シェルの実行」内容の例

#!/bin/bash 
 
### 変数定義 
# ホスト 
HOST_REMOTE=[ホストの名前] 
 
# パス 
PATH_DIST=${WORKSPACE}/dist/ 
PATH_REMOTE=[リモートのパス] 
 
# コマンド 
RSYNC="rsync -rlcv --delete" 
 
 
### ディレクトリ生成 (2回目の実行以降はコメントアウトする) 
# echo -e "\n\n*** ディレクトリ生成 開始 ***" 
# ssh $HOST_REMOTE mkdir -p $PATH_REMOTE 
# echo -e "*** ディレクトリ生成 終了 ***\n" 
 
 
### ファイル転送 
if ${DRYRUN} ; then 
  # dryrun 
  echo -e "\n\n*** dryrun 開始 ***" 
  $RSYNC -n $PATH_DIST $HOST_REMOTE:$PATH_REMOTE 
  echo -e "*** dryrun 終了 ***\n\n" 
else 
  # deploy 
  echo -e "\n\n*** deploy 開始 ***" 
  $RSYNC $PATH_DIST $HOST_REMOTE:$PATH_REMOTE 
  echo -e "*** deploy 終了 ***\n\n" 
fi 


よく使うコマンド

  • echo
  • rsync
  • mkdir
  • rm
  • if then elif then else exit fi
  • for do done
  • test
  • ls
  • grep
  • xargs
  • #
  • #!/bin/bash
次章にてこれらを1つずつ確認する



コマンド解説



echo


文字列や変数の内容を表示する

  • UNIXコマンドの1つ
  • 文字通り「echo」の意味


よく使うオプション

option 内容
-e escapeの頭文字。エスケープ文字を有効にして表示する


サンプルコード

echo -e "\n\n*** dryrun 開始 ***" 


Work


01
以下の動作の違いを確認しましょう

echo あいうえお 
echo  あ\nい\nう\nえ\nお 
echo -e あ\\nい\\nう\\nえ\\nお 
echo  "あ\nい\nう\nえ\nお" 
echo -e "あ\nい\nう\nえ\nお" 


02
変数の働きを確認しましょう

HOGE="あ\nい\nう\nえ\nお" 
echo -e $HOGE 


rsync


テストサーバーや本番サーバーへのファイルアップロードにはおもにrsyncを利用する

  • UNIXソフトウェアの1つ
  • 「remote sync」の意味
  • 転送元パスの「/」の有無が挙動に影響するので注意が必要
  • 転送元パスの「/hoge」はディレクトリ自身を含み、「/hoge/」はディレクトリの中身だけに作用する


よく使うオプション

option 内容
-r, --recursive
指定したディレクトリの下層のディレクトリにも再帰的に実行する
-l, --links
ローカルのシステムと同様にリモートのシステムでシンボリック・リンクを再作成する
-c, --checksum
コピー先に同じチェックサムとサイズを持つ同名のファイルをスキップする
-v, --verbose
転送中の情報を詳しく表示する。このオプションは三つまで連記指定可能
-n, --dry-run
実際には実行しないで、実行したと仮定した場合の情報だけを表示する
--delete コピー先に存在してコピー元に存在しないファイルを削除する
--ignore-existing コピー先に存在するファイルは更新しない
--include 指定したファイルやディレクトリを対象に含む
--exclude 指定したファイルやディレクトリを対象に含まない


サンプルコード

PATH_LOCAL=${WORKSPACE}/dist/ 
PATH_REMOTE=/hoge/ 
rsync -rlcv --delete $PATH_LOCAL $PATH_REMOTE 
rsync -r -l -c -v --delete $PATH_LOCAL $PATH_REMOTE 


Work

以下の動作の違いを確認しましょう

cd [作業するディレクトリ] 
rsync -rlcvn a/ b 
rsync -rlcv a/ b 
rsync -rlcvn a c 
rsync -rlcv a c 
rsync -rlcv a/ d 
rsync -rlcvv a/ e 
rsync -rlcvvv a/ f 


mkdir


新しいディレクトリを作成する

  • UNIXコマンドの1つ
  • 「make directory」の意味


よく使うオプション

option 内容
-p, --parents
親ディレクトリも同時に作成する


サンプルコード

PATH_REMOTE=/hoge/ 
mkdir -p $PATH_REMOTE 


Work

以下の動作を確認しましょう

PATH_REMOTE=e 
mkdir -p $PATH_REMOTE 


rm


指定したディレクトリ以下の全ディレクトリツリーを削除する

  • UNIXコマンドの1つ
  • 「remove」の意味
  • パスの指定を誤るとサーバーが空になるので注意


よく使うオプション

option 内容
-r, -R, --recursive
指定したディレクトリの下層のディレクトリにも再帰的に実行する
-f, --force
警告メッセージを表示せずに実行する


サンプルコード

PATH_BACKUP=${WORKSPACE}/_backup/ 
if test -d $PATH_BACKUP ; then 
  rm -rf $PATH_BACKUP 
fi 


Work

以下の動作を確認しましょう

PATH_REMOTE=e 
rm -rf $PATH_REMOTE 


if [条件]; then [処理] elif [条件]; then [処理] else [処理] fi


条件分岐する

  • シェル・スクリプト構文
  • 条件によって処理を分ける


サンプルコード & Work

HOGE=3 
if [ $HOGE -eq 1 ]; then 
  echo -e "1です" 
elif [ $HOGE -eq 2 ]; then 
  echo -e "2です" 
else 
  echo -e "${HOGE}です" 
fi 


for [イテレータ変数] in [配列] do [処理] done


for文を定義する

  • シェル・スクリプト構文
  • ループ制御文
  • Bに配列、Aにイテレータ変数、Cに処理内容を記述する


サンプルコード & Work

CONTENT_ARR=() 
CONTENT_ARR+=(a/) 
CONTENT_ARR+=(b/) 
CONTENT_ARR+=(c/) 
 
for CONTENT in ${CONTENT_ARR[@]} 
do 
  echo -e "${CONTENT}" 
done 


test


真偽を判定する

  • UNIXコマンドの1つ
  • 条件式を評価し真偽値を返す


よく使うオプション

option 内容
-d directoryの頭文字。指定したファイルが存在し,ディレクトリであれば真を返す


サンプルコード

### WORKSPACEから dist/ と .git/ 以外を削除 
if test -d ${WORKSPACE} ; then 
  ls -A | grep -v "dist" | grep -v ".git" | xargs rm -rf | rm -rf .editorconfig | rm -rf .gitignore | rm -rf bin 
fi 


Work

if test -d a/; then 
  echo -e "存在します" 
else 
  echo -e "存在しません" 
fi 
 
if test -d z/; then 
  echo -e "存在します" 
else 
  echo -e "存在しません" 
fi 


ls


ファイル、ディレクトリの情報取得

  • UNIXコマンドの1つ
  • 「list」の意味
  • ディレクトリやファイルの情報を表示する


よく使うオプション

option 内容
-A ".."や"."を表示しない


サンプルコード

### WORKSPACEから dist/ と .git/ 以外を削除 
if test -d ${WORKSPACE} ; then 
  ls -A | grep -v "dist" | grep -v ".git" | xargs rm -rf | rm -rf .editorconfig | rm -rf .gitignore | rm -rf bin 
fi 


Work

ls ./ 
ls -A ./ 


grep


検索して出力する

  • UNIXコマンドの1つ


よく使うオプション

option 内容
-v, --invert-match
マッチしない行を出力する


サンプルコード

### WORKSPACEから dist/ と .git/ 以外を削除 
if test -d ${WORKSPACE} ; then 
  ls -A | grep -v "dist" | grep -v ".git" | xargs rm -rf | rm -rf .editorconfig | rm -rf .gitignore | rm -rf bin 
fi 


Work

以下の動作の違いを確認しましょう

grep テキスト ./b/*.txt 
grep a ./b/*.txt 
grep b ./b/*.txt 


xargs


前の処理を受けて処理を実行する

  • UNIXコマンドの1つ
  • 「エックスアーグズ」と読む
  • 入力を読み込み、それを引数として指定したコマンドを実行する


サンプルコード & Work

rsync -rlcv a/ b 
ls | grep b | xargs rm -rf 


#


  • UNIXシェルスクリプトの一行コメントアウトの記述に利用する


#!/bin/bash


  • UNIXシェルスクリプトの先頭に書くUNIX命令
  • この命令のことをシバン、シェバン(shebang)と呼ぶ
  • インタプリタを起動する命令
  • シェバン以下に書くコマンドの種類を指定している

  • #!/bin/bash#!/bin/sh#!/bin/csh#!/usr/bin/perl#!/usr/bin/rubyなどがある


02. gitコマンド




68747470733a2f2f73656b69796165696a692e67





68747470733a2f2f73656b69796165696a692e67




Octocat Monalisa

Once upon a time, there was a cat who’s name was Monalisa. Monalisa and her owner went to the beach. When Monalisa got to the beach, her owner Kelly gave her goggles so Monalisa could swim and see what was in the ocean. When Monalisa went in the water she found lots of fish. When Monalisa was swimming she was so exited she opened her mouth and swallowed a coral that made you grow legs like a octopus but keep your normal face. So Monalisa grew legs and became Mrs. Monalisa octocat.

Jenkins の Job でよく利用する gitコマンド を紹介



Jenkins 「シェルの実行」内容の例

### $BRANCH_NAME を checkout 
git checkout $BRANCH_NAME 
 
### $BRANCH_NAME を master に merge 
git checkout master 
git merge $BRANCH_NAME 
git push origin master 
 
### $WORKSPACEを削除 
if test -d ${WORKSPACE} ; then 
  rm -rf ${WORKSPACE} 
fi 


よく使うコマンド

  • clone
  • checkout
  • add
  • commit
  • push
  • merge
  • tag
次章にてこれらを1つずつ確認する



コマンド解説



clone


既存リポジトリのコピーを取得

  • clone コマンドは Jenkins Job内では「ソースコード管理」の「Git」の機能で実行することが多い


サンプルコード & Work

cd [作業するディレクトリ] 
git clone https://github.com/sekiyaeiji/jenkins-demo.git 
cd jenkins-demo 


checkout


ブランチやファイルを取得してその後の処理の準備をする



サンプルコード & Work

git checkout master 
git checkout develop 
git checkout master 


add


インデックスに追加する



よく使うオプション

option 内容
-A 「.」と「-u」を合わせた機能。すべての変更を含む内容を追加登録する


サンプルコード & Work

git add -A 


commit


commit する



サンプルコード & Work

git commit -m "hoge" 


push


push する



サンプルコード & Work

git push origin develop 


merge


merge する



サンプルコード & Work

git checkout master 
git merge develop 


tag


tag を作成する



よく使うオプション

option 内容
-m 作成する tag にメッセージを付与する


サンプルコード & Work

git tag fuga -m "piyo" 
git tag 
git push --tag 


03. 設定



基本設定



68747470733a2f2f73656b69796165696a692e67




ジョブ名(プロジェクト名)

  • 半角英数文字列により命名する
  • その文字列ままジョブのURLとして利用される
  • インスタンス内でユニークである必要がある
  • スペースは原則として使わない(%20になる)


説明

  • html要素がレンダリングできる
  • [プレビュー]押下で作業中のレイアウトが確認できる


古いビルドの破棄

  • Jenkinsインスタンスの容量節約のため設定する
  • 基本的には「ビルドの保存最大数 : 10」を設定しておけば問題ない


Slack Notifications

  • チームでジョブ実行監視を行いたい場合などに有効
  • 特に本番用ビルドが走る場合など、失敗ログにすばやく気付けて便利


手順

  • 「Notify Success」をチェック
  • [高度な設定...]を押下
  • 「Team Domain : [Slackのチームドメイン]」
  • 「Integration Token : [インテグレーショントークン値]」
  • 「Project Channel : [通知したいチャンネル名]」


Git ブランチ選択


Git Parameter



68747470733a2f2f73656b69796165696a692e67




手順

  • 引数名になる「Name : BRANCH_NAME」を入力
  • このジョブのブランチルールの説明などを「Description」に記載
  • 通常のブランチ操作を行うジョブの場合、「Parameter Type : Branch」を選択
  • 扱う頻度の高いブランチやデフォルトブランチを設定する developの場合は「Default Value : origin/develop」


DRYRUN フラグ


真偽値



68747470733a2f2f73656b69796165696a692e67




手順

  • 引数名になる「名前 : DRYRUN」を入力
  • 失敗を許容されないジョブの場合は「デフォルト値」をチェックする テストサーバーではチェックは不要
  • このジョブのDRYRUNの使い方などを「説明」に記載


Git Clone


ソースコード管理 - Git



68747470733a2f2f73656b69796165696a692e67




手順

  • 「ソースコード管理」で「Git」ラジオボタンをチェックする
  • 「Repository URL」にリポジトリのSSHパスを設定

  • 「Branches to build」に、Git Parameterで設定した引数「$BRANCH_NAME」を入力


「シェルの実行」の設定



シェルコマンド

01. シェルコマンドで学んだコマンドで実際の動作を確認しましょう



gitコマンド

02. gitコマンドで学んだコマンドで実際の動作を確認しましょう



04. 「シェルの実行」のバリエーション



dryrun試行 を含む通常の rsync deploy

  • WORKSPACE内の「dist」ディレクトリの内容をリモートにリリースするシェルサンプル

#!/bin/bash 
 
### 変数定義 
# ホスト 
HOST_REMOTE=[ホストの名前] 
 
# パス 
PATH_DIST=${WORKSPACE}/dist/ 
PATH_REMOTE=[リモートのパス] 
 
# コマンド 
RSYNC="rsync -rlcv --delete" 
 
 
### ディレクトリ生成 (2回目以降の実行時はコメントアウトしてもOK) 
# echo -e "\n\n*** ディレクトリ生成 開始 ***" 
# ssh $HOST_REMOTE mkdir -p $PATH_REMOTE 
# echo -e "*** ディレクトリ生成 終了 ***\n" 
 
 
### ファイル転送 
if ${DRYRUN} ; then 
  # dryrun 
  echo -e "\n\n*** dryrun 開始 ***" 
  $RSYNC -n $PATH_DIST $HOST_REMOTE:$PATH_REMOTE 
  echo -e "*** dryrun 終了 ***\n\n" 
else 
  # deploy 
  echo -e "\n\n*** deploy 開始 ***" 
  $RSYNC $PATH_DIST $HOST_REMOTE:$PATH_REMOTE 
  echo -e "*** deploy 終了 ***\n\n" 
fi 


remote からファイルを取得する

  • WORKSPACE内の「_backup」ディレクトリにリモートのファイルを保存するシェルサンプル

#!/bin/bash 
 
### 変数定義 
# ホスト 
HOST_REMOTE=[ホストの名前] 
 
# パス 
PATH_BACKUP=${WORKSPACE}/_backup/ 
PATH_REMOTE=[リモートのパス] 
 
# コマンド 
RSYNC_BACKUP="rsync -rl" 
 
 
### バックアップ保存 
# WORKSPACE内の過去のバックアップを削除 
if test -d $PATH_BACKUP ; then 
  rm -rf $PATH_BACKUP 
fi 
 
# WORKSPACEにバックアップを保存 
echo -e "\n\n*** backup 開始 ***" 
$RSYNC_BACKUP $HOST_REMOTE:$PATH_REMOTE $PATH_BACKUP 
echo -e "*** backup 終了 ***\n\n" 


rsync deploy と remote からのバックアップ保存の組み合わせ

  • おもに本番リリースジョブに利用する
  • 本番 deploy ではこのリリース直前のバックアップを原則として義務づけている
  • DRYRUN の際にバックアップが作動しないよう条件を設定する

#!/bin/bash 
 
### 変数定義 
# ホスト 
HOST_REMOTE=[ホストの名前] 
 
# パス 
PATH_DIST=${WORKSPACE}/dist/ 
PATH_BACKUP=${WORKSPACE}/_backup/ 
PATH_REMOTE=[リモートのパス] 
 
# コマンド 
RSYNC="rsync -rlcv --delete" 
RSYNC_BACKUP="rsync -rl" 
 
 
### ディレクトリ生成 (2回目以降の実行時はコメントアウトしてもOK) 
# echo -e "\n\n*** ディレクトリ生成 開始 ***" 
# ssh $HOST_REMOTE mkdir -p $PATH_REMOTE 
# echo -e "*** ディレクトリ生成 終了 ***\n" 
 
 
### バックアップ保存 
if [ ${DRYRUN} = false ] ; then 
  # WORKSPACE内の過去のバックアップを削除 
  if test -d $PATH_BACKUP ; then 
    rm -rf $PATH_BACKUP 
  fi 
 
  # WORKSPACEにバックアップを保存 
  echo -e "\n\n*** backup 開始 ***" 
  $RSYNC_BACKUP $HOST_REMOTE:$PATH_REMOTE $PATH_BACKUP 
  echo -e "*** backup 終了 ***\n\n" 
fi 
 
 
### ファイル転送 
if ${DRYRUN} ; then 
  # dryrun 
  echo -e "\n\n*** dryrun 開始 ***" 
  $RSYNC -n $PATH_DIST $HOST_REMOTE:$PATH_REMOTE 
  echo -e "*** dryrun 終了 ***\n\n" 
else 
 # deploy 
  echo -e "\n\n*** deploy 開始 ***" 
  $RSYNC $PATH_DIST $HOST_REMOTE:$PATH_REMOTE 
  echo -e "*** deploy 終了 ***\n\n" 
fi 


Work

  • 「RSYNC_BACKUP」のオプション値「-rl」の理由を考察する


WORKSPACE 内に残留するブランチ情報を削除する

  • ジョブの最後に記述する
  • WORKSPACE 内のGitブランチリストを削除することによって、次のビルド表示のブランチリストを最新の状態で取得できるようにする

#!/bin/bash 
 
### WORKSPACE 内に残留するブランチ情報を削除 
# パス 
PATH_GIT_BRANCHES=${WORKSPACE}/.git/refs/remotes/ 
 
# 削除 
if test -d $PATH_GIT_BRANCHES ; then 
  rm -rf $PATH_GIT_BRANCHES 
fi 


バックアップから deploy する

  • 万が一、バックアップからの切り戻しをする場合に利用する
  • 通常、誤操作防止のため通常のデプロイジョブとは別に作成するのが一般的
  • 別のジョブの WORKSPACE を直接参照するため、 PATH_BACKUP に環境変数の ${WORKSPACE} を利用しないことに注意
  • 別の Jenkins インスタンスに設定する場合は PATH_BACKUP のルートパスを変更すること
  • 説明欄の <h2 style="color:red;">【警告】 このジョブは通常運用では決して利用しません 【警告】</h2> のような警告記載により、操作を防止する工夫が必要

#!/bin/bash 
 
### 変数定義 
# ホスト 
HOST_REMOTE=[ホストの名前] 
 
# パス : PATH_BACKUP は特定のジョブのパスを直接指定しています 
PATH_BACKUP=[Jenkins インスタンスのルートパス]/workspace/[リリースジョブのプロジェクト名]/_backup/ 
PATH_REMOTE=[リモートのパス] 
 
# コマンド 
RSYNC="rsync -rlcv" 
 
 
### ファイル転送 
if ${DRYRUN} ; then 
  echo -e "\n\n*** failback dryrun 開始 ***" 
  $RSYNC -n $PATH_BACKUP $HOST_REMOTE:$PATH_REMOTE 
  echo -e "*** failback dryrun 終了 ***\n\n" 
else 
  # deploy 
  echo -e "\n\n*** failback deploy 開始 ***" 
  $RSYNC $PATH_BACKUP $HOST_REMOTE:$PATH_REMOTE 
  echo -e "*** failback deploy 終了 ***\n\n" 
fi 


TAG を打つ

  • リリースTAGを生成する場合などに利用する
  • 「$BRANCH_NAME」 は Git Parameter などから取得する

#!/bin/bash 
 
 
### checkout develop 
# git checkout develop 
git checkout $BRANCH_NAME 
BRANCH_ID="${BRANCH_NAME//origin\/release\//}" 
 
 
### Tag生成 
DATE=`date +%Y%m%d_%H-%M-%S` 
TITLE="release_${DATE}_$BRANCH_ID" 
git tag -a ${TITLE} -m "version : ${DATE}" 
git push --tag 
git tag -d ${TITLE} 


develop を master にマージする

  • develop と master の同期をする場合などに利用する
  • 「$BRANCH_NAME」 は Git Parameter などから取得する

#!/bin/bash 
 
### $BRANCH_NAME を checkout 
git checkout $BRANCH_NAME 
 
### $BRANCH_NAME を master に merge 
git checkout master 
git merge $BRANCH_NAME 
git push origin master 


納品用ブランチ delivery ブランチにコミットする

  • MU納品案件の納品方法の1スタイル
  • delivery という名前のブランチに、build 後の dist の内容だけをコミットする
  • 納品先に diff を送付することにより変更点を伝達できる
  • 「$BRANCH_NAME」 は Git Parameter などから取得する

#!/bin/bash 
 
### 変数 
BRANCH_DELIVERY=delivery 
COMMIT_ID="$(git rev-parse HEAD)" 
 
echo -e "\n\n*** $BRANCH_DELIVERY に $COMMIT_ID をデプロイする ***" 
 
### checkout 
git checkout $BRANCH_DELIVERY 
git checkout origin/$BRANCH_NAME -- dist 
 
### WORKSPACEから dist/ と .git/ 以外を削除 
if test -d ${WORKSPACE} ; then 
  ls -A | grep -v "dist" | grep -v ".git" | xargs rm -rf | rm -rf .editorconfig | rm -rf .gitignore | rm -rf bin 
fi 
 
### distディレクトリをroot展開 
cp -r dist/* . 
rm -rf dist 
 
### commit 
if [ -z "$(git status --porcelain)" ]; then 
  echo -e "\n*** commitすべき差分が存在しません ***\n\n" 
else 
  git add -A 
  git commit -m "delivery branch : $COMMIT_ID" 
  git push origin $BRANCH_DELIVERY 
fi 


WORKSPACE 内で build する (シンボリックリンクを利用する場合)

  • バージョン管理の理想形は、dist をコミットしない状態
  • Jenkins ジョブ内で dist を生成して deploy する
  • 現状、ジョブ実行でnpmインストールできる環境は社内にない
  • /home/node_package/ に事前にインフラにインストール依頼が必要
  • インストールされたパスを SYMBOLIC_LINK に設定する

#!/bin/bash 
 
### 変数定義 
# パス 
PATH_SRC=${WORKSPACE}/src/ 
 
# シンボリックリンク 
SYMBOLIC_LINK=/home/node_package/[プロジェクトディレクトリなど]/node_modules 
 
# コマンド 
BUILD="npm run build" 
 
 
### ビルド 
if test -d $PATH_SRC ; then 
 
  # 指定領域のnode_modulesにシンボリックリンクを張る 
  if test -d node_modules ; then 
    echo -e "*** node_module is exist ***\n\n" 
  else 
    ln -s $SYMBOLIC_LINK 
  fi 
 
  # npm update 
  source ~/.nvm/nvm.sh 
  nvm use v8.9.1 
  node -v 
  nvm ls 
  BABEL_DISABLE_CACHE=1 
 
  # ビルド 
  echo -e "\n\n*** build ***" 
  $BUILD 
  echo -e "*** build done ***\n\n" 
else 
  echo -e "\n\n*** content buildが実行できませんでした ***\n\n" 
  exit 
fi 

コメント

このブログの人気の投稿

投稿時間:2021-06-17 22:08:45 RSSフィード2021-06-17 22:00 分まとめ(2089件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)