何?Swaggerって?

お仕事で「APIの仕様書をすわっがーで作ってね」って言われました、既存の仕様書はYAMLファイルになっているのですが・・・何?Swaggerって?

Swaggerは、REST APIの仕様書を作ってAPIを構築するツールです。

Swagger は RESTful APIを構築するためのオープンソースフレームワークのことです。「Open API Initiative」という団体がRESTful APIインターフェイスの記述をするための標準フォーマットを推進していて、その標準フォーマットがSwaggerです。Swaggerには多くの便利なツールが提供されていることもあり、多くのメリットを享受できそうです。

Swaggerの概要をまとめてみた。 - Qiita

仕様書のフォーマット?

Swaggerは、OpenAPI仕様(以下OAS)と言われる、REST APIを定義するための標準仕様にもとづいて構築された一連のオープンソースツールです。REST APIの設計、構築、文書化、および使用に役立つ機能を提供します。

本当に使ってよかったOpenAPI (Swagger) ツール | フューチャー技術ブログ

フォーマットだけじゃなくて、仕様書から構築までできるらしいです。

ブラウザで仕様書を見て書けます。

ツールをインストールしたり環境を構築したりしなくてもすぐに使えそうです。

  1. Swagger Editorをブラウザで表示する
    • f:id:ponsuke_tarou:20210121141523p:plain
  2. [File] > [Import file] > 既存の仕様書をインポートする
    • f:id:ponsuke_tarou:20210121141129p:plain
  3. 仕様書に定義されたインターフェースが横に見やすく表示された!
  4. 左のYMLを修正すると右側に反映される

実行もできます。

呼出し先が起動していれば、必要な情報を入力して実行することもできました。

  1. 仕様書のAPIを起動して接続できるようにする
  2. f:id:ponsuke_tarou:20210121094641p:plain
    このボタンを押下すると入力できるようになる
  3. ヘッダやパラメータなど必須になっている項目を入力する
  4. f:id:ponsuke_tarou:20210121094746p:plain
    このボタンで実行できる
  5. 「TypeError」になったけれどAPIのログを見てみるとちゃんとAPIを呼び出していた
    • f:id:ponsuke_tarou:20210121142535p:plain
      Server responseにAPIからのレスポンスが表示される

GitにあるSwaggerのファイルをプレビューできるChrome拡張機能があるらしいです。

qiita.com

Serversを書く

APIのサーバを定義します。 サーバには、本番環境やらテスト環境など複数を定義できます。

f:id:ponsuke_tarou:20210122163707p:plain
serversを複数定義するとプルダウンで選べるようになりました。

認証を書く

試しに「OAuth2.0で取得したBearerのトークンをヘッダにAuthorizationで設定する」みたいに書こうとした・・・ら、なんか言われました。

Header parameters name "Authorization" are ignore. Use the `securitySchemes` and `security` sections instead to define authorization.

f:id:ponsuke_tarou:20210121161236p:plain どうやら、ヘッダやパラメータに「Authorization」を設定しても無視されるようです。

You have used a restricted value as the name of a header parameter. The values Accept, Content-Type, and Authorization are restricted values and should not be used as the header name. A header with any of these values as the header name is ignored.

Header parameter with the name 'Authorization' is ignored - apisecurity.io

では、どう書くのか?調べながら書いてみました。

Bearer スキームを書く

トークンを利用した認証・認可 API を実装するとき Authorization: Bearer ヘッダを使っていいのか調べた - Qiitaを読むとやりたいことは「Bearer スキーム」というものでした。

f:id:ponsuke_tarou:20210121164741p:plain
[Authorize]ボタンが表示されてトークンを設定できるダイアログが表示されるようになりました

YAMLファイルに保存する

ブラウザ上で書いたものをローカルにダウンロードしてYAMLファイルとして保存します。

f:id:ponsuke_tarou:20210121171039p:plain
[File] > [Save as YAML]から簡単に保存できる

はまゆう日記

基本情報

我が家に来た経緯

お向かいさんがはまゆうの種を玄関先で配っていたのでたくさんいただいてみた。

f:id:ponsuke_tarou:20210110102433j:plain
2021-01-10:玄関で管理中

分類

  • 学名:Crinum asiaticum L.
    • 日本に自生するのは亜種のCrinum asiaticum var. japonicum
  • APG体系 : キジカクシ目Asparagales > ヒガンバナ科Amaryllidaceae > ハマオモト属Crinum > ハマユウC. asiaticum
  • 別名:浜木綿(ハマユウ)、浜万年青(ハマオモト)
  • 常緑 / 多年草 / 半耐寒性

生態 : 鉢植えにして寒くなったら家に入れる

  1. 温暖な海岸砂地に自生する大形常緑の多年草
  2. 花茎:約50~100cm(葉:50~80cm)
    • でかい・・・うちのぷてぃーとな花壇には入らない・・・
  3. 葉の間の真ん中から太くてまっすぐな茎を上に伸ばし、先端に白い花弁が25~40個ほど集まって、散形花序を作る
  4. 根(鱗茎)に有毒なリコリン(アルカロイド)を含む
    • 食べるとよだれが出て、吐き気、下痢、血圧低下、中枢神経の麻痺などの症状が現れる
    • しかし、ハマオモトヨトウはハマユウを食べる
時期 補足
播種 種を採取した後すぐ 種から花が立派につくようになるまで数年かかる
肥料 4月(発芽) 緩効性化成肥料
苗植え 4~8月 or 11月
花期 7~9月 花は夕方から開き始め深夜に満開になる。よい香りを放つ
肥料 9月(花の終わり) 緩効性化成肥料

https://sakata-tsushin.com/oyakudachi/lesson/flower/assets_c/2016/05/crinum_calendar-thumb-960x450-4840.jpg リナムの育て方・栽培方法|失敗しない栽培レッスン(花の育て方)|サカタのタネ 家庭菜園・園芸情報サイト 園芸通信

(土)赤玉土7:腐葉土3

日当たり 水はけ 乾燥 寒さ 暑さ
朝から晩まで日陰にならない場所が 良い 弱い 弱い

https://www.sakataseed.co.jp/product/topics/file/image/%E7%90%83%E6%A0%B9/%E3%82%A4%E3%83%B3%E3%83%89%E3%81%AF%E3%81%BE%E3%82%86%E3%81%86%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88.gif

クリナム (プレミアム野放し球根)|商品情報いろいろ検索|タネ・苗・園芸用品・農業用資材の総合案内:サカタのタネ

MacでJSFのプロジェクトを作る

  • 環境
    • macOS Big Sur バージョン11.0.1
    • openjdk version "11.0.8" 2020-07-14

以前、CentOSJSFのプロジェクトを作ったので今回はMacでつくる

ponsuke-tarou.hatenablog.com

CentOSでせっかく作っても・・・Dockerイメージを取らずに、Gitにコミットせずに・・・うっかりEC2インスタンスもろとも削除してしまいました。 というわけでMacで再び作ります。

f:id:ponsuke_tarou:20201221231926j:plain
豊島区の目白庭園

Eclipseを配置する

  1. Mac 版 Eclipse Pleiades All in One リリース - Qiitaを参考にEclipseを配置する
  2. eclipse.iniで以前インストールしたJava11を設定する
オプション 意味 参考
-vm JVM(Javaプログラムを動かすためのソフトウェア)のパスを設定 eclipse.iniに-vmを指定する方法 - Qiita
--illegal-access=deny コマンドで起動するときに出るワーニングを抑制 警告を出ないようにする - Qiita
#...省略...以下追記箇所...
-vm
/usr/local/opt/openjdk@11/bin/java
-vmargs
--illegal-access=deny
#...省略...

Payaraをインストールする

  1. zipでインストールする - Qiita
  2. Eclipseに設定する - Qiita

Mavenプロジェクトを作成する

  1. [パッケージ・エクスプローラー]にカーソルを入れて「Ctrl + N」で新規作成ダイアログを表示する。
  2. [Maven] > [Mavenプロジェクト] > [次へ]ボタン
  3. [シンプルなプロジェクトの作成(アーキタイプ選択のスキップ)]チェックボックスをONにする > [次へ]ボタン
  4. 以下を設定して[完了]ボタンでプロジェクトを作成する

pom.xml文字コードUTF-8」を設定する

文字コードを設定しないと各OSの文字コードでコードがビルドされます。 そうなるとビルドする環境によって内容が変わってしまいます。

なので、プラットフォームのエンコーディング (実際は UTF-8) を使用してフィルターされたリソースをコピーします。つまり、ビルドはプラットフォームに依存します!というメッセージがログや[エラー・ログ]ビューに出力されます。

<!-- ...省略... -->
  <properties>
    <!-- ソースの文字コードを定義 -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
</project>

コンパイラを設定する

  1. [パッケージ・エクスプローラー]でプロジェクトを選択 > 「Command + I」でプロパティダイアログを表示する
  2. [Java コンパイラー] > [Javaビルド・パス上の実行環境'J2SE-1.5'から準拠を使用]チェックボックスをOFFにする
  3. [コンパイラー準拠レベル]で「11」を選択
  4. [デフォルトの準拠設定の使用]チェックボックスをOFFにする
  5. [適用]ボタン > メッセージダイアログが表示されるので[はい]でビルドを行う f:id:ponsuke_tarou:20201229095524p:plain

pom.xmlMavenコンパイル用のJDKを定義する

pom.xmlを開いてJDKを以下のように定義します。

JDKを定義しないとMavenビルド後にJava compiler level does not match the version of the installed Java project facet.というエラーになることがあります。

<!-- ...省略... -->
  <properties>
    <!-- ソースの文字コードを定義 -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- Mavenコンパイル用のJavaを定義 -->
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>
</project>

f:id:ponsuke_tarou:20201229115210j:plain
台東区の鶴の湯

JSFを設定する

  1. [パッケージ・エクスプローラー]でプロジェクトを選択 > 「Command + I」でプロパティダイアログを表示する
  2. [プロジェクト・ファセット] > [ファセット・フォームへ変換...]リンクから一覧を表示する
  3. [Java]をONにして[Version]を「11」にする
  4. [JavaServer Faces]をONにして[Version]を「2.2」にする
    • ここを「2.3」にすると後でコードをサーバで実行するときにUnable to find CDI BeanManager.となることがあるので注意してください
  5. [動的Webモジュール]をONにして[Version]を「3.1」にする
    1. [ランタイム]タブ > 表示されたPayaraをONにする
    2. 下の方に出てくる[より詳しい構成が必要...]リンクを押下して[ファセット・プロジェクトの変更]画面を表示する
      • f:id:ponsuke_tarou:20201229101314p:plain
    3. [コンテキストルート : ]を設定する(今回はデフォルトのまま)
      • Content directoryは、HTMLやCSSや画像ファイルなどのコンテンツを格納するディレクトリルート
    4. [web.xmlデプロイメント記述子の生成 : ]チェックボックスをONにする > [次へ]ボタン
      • f:id:ponsuke_tarou:20201216225927p:plain
    5. [URLマッピング・パターン:]で「/faces/」を[除去]して、「.jsf」「*.xhtml」を[追加...]する
      • f:id:ponsuke_tarou:20201229101140p:plain
    6. [OK]ボタンでダイアログを閉じる
  6. [適用して閉じる]ボタンでプロパティダイアログを閉じる
  7. メッセージダイアログが表示されるので[はい]でビルドを行う
    • f:id:ponsuke_tarou:20201216230210p:plain

f:id:ponsuke_tarou:20201229101623p:plain
この設定によりWebContentsディレクトリやweb.xml、faces-config.xmlが作成されます。

WebContentディレクトリは、コンテキストディレクトリともいいます。 コンテキストディレクトリは、任意のディレクトリに変更することもできます。

中身はこんな感じです。(まだないものもあります)

ディレクトリ/ファイル名 内容
WebContent/WEB-INF コンパイル済みのプログラムや各種のライブラリファイル、設定ファイルなどが入ります。
このディレクトリ配下のリソースは、クライアント(WEBブラウザ)からアクセスすることはできません。
WebContent/WEB-INF/lib 各種ライブラリのJARファイル。
ここに配置したJARファイル中のクラスファイルはWebアプリケーションから参照することができます。
WebContent/WEB-INF/classes 作成したプログラムのclassファイルやメッセージプロパティファイルが格納されます。
WebContent/WEB-INF/faces-config.xml JSF の構成ファイルです。エラー・メッセージの国際化などに使用するリソース・バンドルの情報が記述されています。
WebContent/WEB-INF/web.xml アプリケーションの動作を指定する必須ファイルです。
FacesServletの動作環境や条件を定義します。
WebContent/resources 画像やCSSなどWebページに読み込ませるデータを配置します。

出力されたweb.xmlはこんな感じです。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>tryJSF</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.jsf</url-pattern>
    <url-pattern>*.xhtml</url-pattern>
  </servlet-mapping>
</web-app>

出力されたfaces-config.xmlはこんな感じです。

<?xml version="1.0" encoding="UTF-8"?>
<faces-config
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
    version="2.2">

</faces-config>

pom.xmlJSFのライブラリを定義する

  1. Maven Repository: org.glassfish » javax.facesから任意のバージョンのMaven用定義をコピーしてpom.xmlに定義を貼り付ける
    • 今回は作業時点で最新の「2.4」を使う
  2. Maven Repository: org.primefaces » primefacesから任意のバージョンのMaven用定義をコピーしてpom.xmlに定義を貼り付ける
    • 今回は作業時点で最新の「8.0」を使う
    • 参考 : Primefacesの紹介
  3. [Package Explorer]でプロジェクトを選択 > 「fn + option + F5」でダイアログを表示 > [OK]ボタンでMavenを更新する
<!-- ...省略... -->
  </properties>
  <dependencies>
    <!-- https://mvnrepository.com/artifact/org.glassfish/javax.faces -->
    <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>javax.faces</artifactId>
        <version>2.4.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.primefaces/primefaces -->
    <dependency>
      <groupId>org.primefaces</groupId>
      <artifactId>primefaces</artifactId>
      <version>8.0</version>
    </dependency>
  </dependencies>
</project>

最初に表示されるページを作成する

JSFのページはWebContentディレクトリにXHTMLで作成します。

index.xhtmlを作成する

  1. [パッケージ・エクスプローラー]で[WebContent]を選択して「Command + N」で新規ダイアログを開く
  2. [Web] > [HTML ファイル] > [次へ]ボタン > [ファイル名:]に「index.xhtml」を入力し[次へ]ボタン
  3. [テンプレート:] > [新規Faceletテンプレート] > [完了]ボタンで新規ページを作成する f:id:ponsuke_tarou:20201221230531p:plain

今はとりあえずなのでindex.xhtmlはテンプレートのままにします(改行は調整済み)。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
  <head>
    <title><ui:insert name="title">Default title</ui:insert></title>
  </head>
  <body>
    <ui:debug hotkey="x" rendered="#{initParam['javax.faces.FACELETS_DEVELOPMENT']}"/>
    <div id="header">
      <ui:insert name="header">
        Header area. See comments below this line in the source.<!-- include your header file or uncomment the include below and create header.xhtml in this directory --> <!-- <ui:include src="header.xhtml"/> -->
      </ui:insert>
    </div>
    <div id="content">
      <ui:insert name="content">
        Content area. See comments below this line in the source. <!-- include your content file or uncomment the include below and create content.xhtml in this directory --> <!-- <div> --> <!-- <ui:include src="content.xhtml"/> --> <!-- </div> -->
      </ui:insert>
    </div>
    <div id="footer">
      <ui:insert name="footer">
        Footer area. See comments below this line in the source. <!-- include your header file or uncomment the include below and create footer.xhtml in this directory --> <!--<ui:include src="footer.xhtml"/> -->
      </ui:insert>
    </div>
  </body>
</html>

index.xhtmlをウェルカムページに設定する

ファイル名を指定しなかった場合に、既定で返されるドキュメントは設定ファイルで指定することが出来ます。 この既定のファイルのことを、ウェルカムページ (Welcome page) といいます。

JSP のウェルカムページ (デフォルトページ) の設定 - Java による Web アプリケーション開発 - Java の基本 - Java 入門

web.xmlにあるwelcome-file-listタグ配下を以下のように変更します。

<!-- ...省略... -->
  <welcome-file-list>
    <welcome-file>index.jsf</welcome-file>
  </welcome-file-list>
<!-- ...省略... -->

Payaraを起動する

  1. [パッケージ・エクスプローラー]でプロジェクトを選択 > Option + Shift + X + R(サーバーで実行)
  2. ダイアログでPayaraを選択 > [OK]ボタンで実行
  3. ブラウザでhttp://localhost:8080/tryJsf/にアクセスしてページが表示されたら動作確認完了

f:id:ponsuke_tarou:20201229114256p:plain

f:id:ponsuke_tarou:20201221232307j:plain
世田谷の用賀にある栄湯

Cloud9でLambdaを作ろうとして失敗した記録

残念ながら

このページは残念な記録しかないので、こういうことが起こるんだぁぐらいにしか役立ちません。 解決方法もありません。誰かに教えてほしい状態です。

Command failed: virtualenv venv -p python3.7

$ python -V
Python 3.6.12
$ python -m pip -V
pip 20.3.1 from /home/ec2-user/.local/lib/python3.6/site-packages/pip (python 3.6)

$ sudo python -V
Python 3.6.12
$ sudo pip -V
pip 9.0.3 from /usr/lib/python3.6/dist-packages (python 3.6)

作成内容

f:id:ponsuke_tarou:20201207204413p:plain

エラー

f:id:ponsuke_tarou:20201207204425p:plain

原因 : 不明

# インストールディレクトリを見てみると
$ which python
/usr/bin/python
# Pythonは「2.7」「3.6」はあるが「3.7」はない
$ ls -la /usr/bin/ | grep python
lrwxrwxrwx  1 root root          24 Dec  7 09:52 python -> /etc/alternatives/python
lrwxrwxrwx  1 root root          17 Dec  4 16:25 python2 -> /usr/bin/python27
-rwxr-xr-x  1 root root        5104 Nov  2 22:27 python27
-rwxr-xr-x  1 root root        5104 Nov  2 22:27 python2.7
-rwxr-xr-x  1 root root        1846 Nov  2 22:27 python2.7-config
lrwxrwxrwx  1 root root          25 Dec  4 16:26 python3 -> /etc/alternatives/python3
-rwxr-xr-x  3 root root        6872 Aug 31 18:58 python36
-rwxr-xr-x  3 root root        6872 Aug 31 18:58 python3.6
lrwxrwxrwx  1 root root          17 Dec  4 16:26 python3.6-config -> python3.6m-config
-rwxr-xr-x  3 root root        6872 Aug 31 18:58 python3.6m
-rwxr-xr-x  1 root root         173 Aug 31 18:57 python3.6m-config
-rwxr-xr-x  1 root root        3373 Aug 31 18:41 python3.6m-x86_64-config
lrwxrwxrwx  1 root root          32 Dec  4 16:26 python3-config -> /etc/alternatives/python3-config
lrwxrwxrwx  1 root root          31 Dec  7 09:52 python-config -> /etc/alternatives/python-config

Python3.7をインストールしてもダメだった

参考 : pyenvによる仮想Python環境をAWS Cloud9上で構築する | Developers.IO

#### pyenvをインストールする
# pyenvをGitHubからCloneする
$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
Cloning into '/home/ec2-user/.pyenv'...
remote: Enumerating objects: 18376, done.
remote: Total 18376 (delta 0), reused 0 (delta 0), pack-reused 18376
Receiving objects: 100% (18376/18376), 3.67 MiB | 2.61 MiB/s, done.
Resolving deltas: 100% (12514/12514), done.
# バージョンを確認する
$ ~/.pyenv/bin/pyenv --version
pyenv 1.2.21-1-g943015eb
# .bashrcに定義を書いて
$ vi ~/.bashrc
$ cat ~/.bashrc | grep pyenv
export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
# 反映させてPATHを通す
$ source ~/.bashrc
$ printenv PATH | sed -e 's/:/:\n/g' | grep pyenv
/home/ec2-user/.pyenv/bin:
$ pyenv --version
pyenv 1.2.21-1-g943015eb



#### Python3.7をインストールする
# インストールできるPython3.7を確認する
$ pyenv install -l | grep 3.7
  2.3.7
  3.3.7
  3.7.0
  3.7-dev
  3.7.1
  3.7.2
  3.7.3
  3.7.4
  3.7.5
  3.7.6
  3.7.7
  3.7.8
  3.7.9
  miniconda-3.7.0
  miniconda3-3.7.0
  stackless-3.3.7
  stackless-3.7.5
# Python3.7.9をインストールする
$ pyenv install 3.7.9
Downloading Python-3.7.9.tar.xz...
-> https://www.python.org/ftp/python/3.7.9/Python-3.7.9.tar.xz
Installing Python-3.7.9...
python-build: use readline from homebrew
WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
WARNING: The Python readline extension was not compiled. Missing the GNU readline lib?
Installed Python-3.7.9 to /home/ec2-user/.pyenv/versions/3.7.9
# WARNINGで出ている不足したものをインストールする
$ sudo yum -y install bzip2 readline
Loaded plugins: priorities, update-motd, upgrade-helper
amzn-main                                                                                   | 2.1 kB  00:00:00     
amzn-updates                                                                                | 3.8 kB  00:00:00     
1067 packages excluded due to repository priority protections
Package bzip2-1.0.6-8.12.amzn1.x86_64 already installed and latest version
Package readline-6.2-9.14.amzn1.x86_64 already installed and latest version
Nothing to do
# Python3.7に切り替える
$ pyenv versions
* system (set by /home/ec2-user/.pyenv/version)
  3.7.9
$ pyenv global 3.7.9
$ python -V
Python 3.7.9


### ターミナルでエラーになったコマンドは実行できる・・・でもLambda関数が作れない・・・
$ virtualenv venv -p python3.7
Running virtualenv with interpreter /home/ec2-user/.pyenv/shims/python3.7
Using base prefix '/home/ec2-user/.pyenv/versions/3.7.9'
New python executable in /home/ec2-user/environment/venv/bin/python3.7
Also creating executable in /home/ec2-user/environment/venv/bin/python
Installing setuptools, pip, wheel...
done.

Sorry, IKPdb only supports Python 3.6.x for now.

  • 環境
    • EC2 instance / Amazon Linux 2
    • Python 3.7.9
    • pip 20.3(rootのpipはアップグレードする等々していて紛失中)
$ python -V
Python 3.7.9

$ pip -V
pip 20.3 from /home/ec2-user/.local/lib/python3.7/site-packages/pip (python 3.7)

$ sudo python -V
Python 2.7.18

$ sudo pip -V
sudo: pip: command not found

作成内容

f:id:ponsuke_tarou:20201207153031p:plain
ランタイムをPython3.7にしたくてblueprintで「microservice-http-endpoint」を選択した

エラー

The following error was encountered when attempting to create your serverless application
Command failed: venv/bin/pip install ikp3db==1.1.4
ERROR: Command errored out with exit status 1:
command: /home/ec2-user/environment/a/venv/bin/python3.7 -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-9w9degcv/ikp3db_0b4451da9ebd4366a50f7baed6b7014c/setup.py'"'"'; __file__='"'"'/tmp/pip-install-9w9degcv/ikp3db_0b4451da9ebd4366a50f7baed6b7014c/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /tmp/pip-pip-egg-info-va_wyotl
cwd: /tmp/pip-install-9w9degcv/ikp3db_0b4451da9ebd4366a50f7baed6b7014c/
Complete output (1 lines):
Sorry, IKPdb only supports Python 3.6.x for now.
----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

f:id:ponsuke_tarou:20201207153233p:plain

原因 : 不明

そもそもIKPdbとはなんぞや?

これには ikpdb という名前のモジュールが含まれており、AWS Cloud9 はこれを使用して Python アプリケーションをデバッグします。

との共同作業 AWS Lambda の関数 AWS Cloud9 Integrated Development Environment (IDE) - AWS Cloud9

Python2.7しかサポートしないよ的なことを言っている気がします。

Please note that IKPdb supports only CPython 2.7, CPython 3 support is the next step.

Welcome to IKPdb’s documentation! — IKPdb 1.0.0 documentation

いや、Python3以降は「IKPdb」ではなく「IKP3db」を使うのか?エラーもikp3db==1.1.4になっている。

IKP3db is a Python 3 debugger. For Python 2 see the IKPdb project on github and pypi.

ikp3db · PyPI

「IKP3db」はバージョン「1.3」以降でPython3.7に対応している・・・のかな?

1.3

Add Python 3.7 support (debugger can now be invoked using the breakpoint() function).

Ikp3db Changelog - pyup.io

対応 : あきらめる

Lambdaの画面から関数を作成しようっと

Command failed: virtualenv venv -p python3.6

  • 環境
    • EC2 instance / Amazon Linux 2
    • Python 3.7.9
    • pip 20.3(rootのpipはアップグレードする等々していて紛失中)
$ python -V
Python 3.7.9

$ pip -V
pip 20.3 from /home/ec2-user/.local/lib/python3.7/site-packages/pip (python 3.7)

$ sudo python -V
Python 3.7.9

$ sudo pip -V
sudo: pip: command not found

作成内容

f:id:ponsuke_tarou:20201207142629p:plain
[Select runtime]にPythonが「3.6」しかないので選択Python3.6を指定した

エラー

f:id:ponsuke_tarou:20201207142743p:plain

原因 : 不明

# インストールディレクトリを見てみると
$ which python
alias python='python3'
        /usr/bin/python3
# Pythonは「2.7」「3.7」はあるが「3.6」はない
$ ls -la /usr/bin/ | grep python                                                                                                                                                      
lrwxrwxrwx  1 root root          24 Dec  7 05:07 python -> /etc/alternatives/python
lrwxrwxrwx  1 root root           9 Nov  6 19:45 python2 -> python2.7
-rwxr-xr-x  1 root root        7048 Aug 27 21:23 python2.7
-rwxr-xr-x  1 root root        1846 Aug 27 21:23 python2.7-config
lrwxrwxrwx  1 root root          16 Nov  6 19:45 python2-config -> python2.7-config
lrwxrwxrwx  1 root root           9 Nov  6 19:57 python3 -> python3.7
-rwxr-xr-x  2 root root        7048 Aug 27 22:02 python3.7
lrwxrwxrwx  1 root root          17 Nov  6 19:57 python3.7-config -> python3.7m-config
-rwxr-xr-x  2 root root        7048 Aug 27 22:02 python3.7m
-rwxr-xr-x  1 root root         173 Aug 27 22:02 python3.7m-config
-rwxr-xr-x  1 root root        3210 Aug 27 21:16 python3.7m-x86_64-config
lrwxrwxrwx  1 root root          16 Nov  6 19:57 python3-config -> python3.7-config
lrwxrwxrwx  1 root root          14 Nov  6 19:45 python-config -> python2-config

対応 : Python3.6にこだわりがないのであきらめる

  1. Python2からPython3へ自力でバージョンアップしてYumが壊れたので修理する
  2. yumをアップデート
  3. Python3.6を探す >> インストールできそうなものがわからないので面倒くさくてあきらめた
$ sudo yum -y update
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                                                                                             | 3.7 kB  00:00:00     
...
Complete!

$ yum search python36-dev
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
220 packages excluded due to repository priority protections
========================================================================================= N/S matched: python36-dev ==========================================================================================
boost-python36-devel.x86_64 : Shared object symbolic links for Boost.Python 3
shiboken-python36-devel.x86_64 : Development files for shiboken

  Name and summary matches only, use "search all" for everything.

Backlogの課題にGitHubのコミットを連携する方法

このブログはBacklog Advent Calendar 2020 の7日目の記事です。 はじめてのAdvent Calendar参加でドキドキです。 adventar.org

Backlogの課題にGitHubのコミットやプルリクをコメントとして入れたい!

やりたいことは、「Backlogの課題にGitHubのコミットやプルリクをコメントとして入れたい」です。 BacklogのGitを使っていればコミットコメントに課題キーがあればその課題のコメントにコミットが連携されます。 なのでGitHubを使っていても同じようにしたい!

[GithubとBacklogの連携] Backlogでissue管理して、Githubへのコミット内容をBacklogにも反映させる様に連携する方法 - Qiitaを見て簡単にできる!とおもったら・・・GitHubのServiceはWebhookに統合されて消えていました。

We have deprecated GitHub Services in favor of integrating with webhooks. Replacing GitHub Services | GitHub Developer Guide

似たようなことをやっている人は世の中にいるのでいろいろ調べながら手作りすることにしました。

f:id:ponsuke_tarou:20201130170903j:plain
流れはこんなイメージ

GitHubとBacklogを連携するLambda関数を作る

1. BacklogでAPIキーを発行する

BacklogではAWSからやってくる処理を受け取るためのAPIキーを発行します。

f:id:ponsuke_tarou:20201130170932j:plain
絵だとこの辺のことです

  1. [個人設定]の画面を開く
    • f:id:ponsuke_tarou:20201119132811p:plain
      [個人設定]は右上のメニューから
  2. [API] > コメントを入力 > [登録]ボタンでAPIキーを発行する
    • f:id:ponsuke_tarou:20201119133053p:plain
      [登録]ボタンで一覧にAPIキーが追加される

2. AWSでLambdaとAPI Gatewayを作成する

AWSではGitHubからやってくる情報をAPI Gatewayで受け取ってLambda関数を呼び出して処理できるようにします。

f:id:ponsuke_tarou:20201130171001j:plain
絵だとこの辺のことです

IAM ポリシーを作成する

Lambda関数で使用する権限を作成します。

  1. [AWS マネジメントコンソール]から[IAM]の画面を開く
  2. サイドメニューの[ポリシー] > [ポリシーの作成]ボタンで作成画面を表示
  3. [JSON]タブを開いて以下のJSONを設定 > [ポリシーの確認]ボタンで内容を確認
  4. [名前]に任意の値を設定 > [ポリシーの作成]ボタンで作成する
    • f:id:ponsuke_tarou:20201130154133p:plain
      Lambdaのログを作成する権限とSecrets Managerからシークレットを取得する権限を設定している
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:対象のリージョン:{アカウントID}:*"
        },
        {
            "Action": "secretsmanager:GetSecretValue",
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

IAM ロールを作成する

Lambda関数にアタッチするロールを作成してさっき作ったポリシーを設定します。

  1. [AWS マネジメントコンソール]から[IAM]の画面を開く
  2. サイドメニューの[ロール] > [ロールの作成]ボタンで作成画面を表示
  3. [AWSサービス] > [Lambda]を選択後に[次のステップ: アクセス権限]ボタンで次の画面を表示
  4. [ポリシーのフィルタ]で作成したポリシーを検索して選択後に[次のステップ: タグ]ボタンで次の画面を表示
  5. [タグの追加 (オプション)]は任意なので設定せずに[次のステップ: 確認]ボタンで次の画面を表示
  6. [ロール名]を入力して[ロールの作成]ボタンでロールを作成する

Lambda関数をとりあえず作る

まずは、実装を後回しにして関数だけ作ります。

  1. [AWS マネジメントコンソール]から[Lambda]の画面を開く
  2. [関数の作成]ボタンで作成画面を表示する
  3. [一から作成]を選択して以下を設定して[関数の作成]ボタンで関数を作成する
    • 関数名 : 任意の名前(今回はgithub_to_backlog)
    • ランタイム : Python3.7
    • 実行ロール : 既存のロールを使用する
    • 既存のロール : 作成したロールを選択

コードには初期コードがあるのでそのまま。実装は後でやります。

import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

API Gatewayを作成する

今回は、[HTTP API]と[REST API]のどちらを使おうか迷ったけれど、「使ったことがない」「GitHubのWebhookを受け取りたいだけ」なんといっても「低コストらしいぞ」という理由で[HTTP API]にしました。

  1. [AWS マネジメントコンソール]から[API Gateway]の画面を開く
  2. [APIを作成]ボタンで[APIの作成]画面を表示する
  3. [HTTP API]の[構築]ボタンで次の画面へ
  4. [統合を追加] > [Lambda] > [Lambda 関数]で作成したLambda関数を選択
  5. [API 名]に任意の名前を設定して[次へ]ボタンで[ルートを設定]画面へ
    • f:id:ponsuke_tarou:20201119161251p:plain
  6. 以下を設定して[次へ]ボタンで[ステージを定義]画面へ
    • メソッド : POST
    • リソースパス : /{Lambda関数名}
    • 統合ターゲット : {Lambda関数名}
    • f:id:ponsuke_tarou:20201119161434p:plain
  7. [ステージを追加]ボタンで以下を追加して[次へ]ボタンで[確認して作成]画面へ
    • ステージ名 : $default
    • 自動デプロイ : ON
    • f:id:ponsuke_tarou:20201119161459p:plain
  8. [作成]ボタンで作成する
    • f:id:ponsuke_tarou:20201119161603p:plain
  9. 「{Lambda関数名}のステージ」一覧の[URLを呼び出す]列に「呼び出しURL」が表示される
  10. curlコマンドを使ってAPI Gatewayを呼び出してAPI GatewayがLambda関数を呼び出せることを確認する
curlコマンドのオプション 意味
-X HTTPメソッドを指定する
-H HTTPヘッダを指定する
# Lambda関数の初期コードに書いてある「Hello from Lambda!」が返却される
$ curl -X POST -H 'Content-Type:application/json' {呼び出しURL}/{リソースパス}
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
}
100    20  100    20    0     0     50      0 --:--:-- --:--:-- --:--:--    50"Hello from Lambda!"

# 失敗例) 「リソースパス」をくっつけ忘れると「Not Found」になるので注意
$ curl -X POST -H 'Content-Type:application/json' {呼び出しURL}
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    23  100    23    0     0    287      0 --:--:-- --:--:-- --:--:--   291{"message":"Not Found"}

3. GithubでWebhook設定する

GitHubでのプッシュやプルリクの情報がAPI Gatewayに送られるようにWebhookを設定します。

f:id:ponsuke_tarou:20201130171025j:plain
絵だとこの辺のことです

  1. ブラウザでGitHubリポジトリを表示する
  2. [Settings] > [Webhooks] > [Add webhook]ボタン
  3. 以下を設定して[Add webhook]ボタン
    • Payload URL : API Gatewayの「呼び出しURL/リソースパス
    • Content type : application/json
    • Secret : 推測されにくい任意の文字列
    • SSL verification : Enable SSL verification
    • Which events would you like... : 「Let me select individual events.」にして以下を選択
      • Pull requests
      • Pushes
    • f:id:ponsuke_tarou:20201119151909p:plain
  4. 表示されたWebhookの横につくマークが緑チェックになるのを確認する
    • URLが間違っていたりすると赤バツが付くので、その場合は内容を確認する
    • f:id:ponsuke_tarou:20201119164221p:plain

4. AWSでSecrets Managerに情報を登録する

BacklogのAPIキーGitHubのWebhookに設定したSecretは大切な情報なのでSecrets Managerに登録して、Lambda関数から取得して使うようにします。

f:id:ponsuke_tarou:20201130171045j:plain
絵だとこの辺のことです

  1. [新しいシークレットを保存する]ボタンから作成画面を表示して以下を設定して[次]ボタン
    • シークレットの種類: その他のシークレット
    • シークレットのペア: 下記表参照
      • f:id:ponsuke_tarou:20201130160504p:plain
        Backlogの情報は1つのシークレットに2つのキーを設定する
    • 暗号化キー: DefaultEncryptionKey
  2. 以下を設定して[次]ボタン
    • シークレットの名前: 下記表参照
    • 説明とタグ: 任意
  3. [自動ローテーションを無効にする]を設定して[次]ボタン
  4. [保存]ボタンでシークレットを作成する
シークレットの名前 シークレットキー シークレットの値 使うところ
github/to/backlog GITHUB_SECRET GitHubのWebhookに
設定したSecret
GitHubから来た情報
をHMAC認証する時に使う
backlog/{GitHubのusername} APIKEY BacklogのAPIキー Backlogに各ユーザで
コメント追加するのに使う
同上 MAIL Backlogに登録され
ているメールアドレス
(以下手順で確認)
1.Backlogの[個人設定]の画面を開く
2. [ユーザー情報] > [メールアドレス]
プルリクエストの通知を
つけるためのid取得に使う

5. Lambda関数を実装する

環境変数を設定する

Backlogの課題にコメント追加するにはBacklog APIを使用します。 そのためにBacklog APIの情報としてLambdaの環境変数に以下を設定して使用します。

環境変数のキー 説明
BACKLOG_ENDPOINT BacklogAPIのエンドポイント https://BacklogのURLと同じ値/api/v2/
参考:認証と認可 | Backlog Developer API | Nulab
PROJECT_KEY Backlogのプロジェクトキー 参考 : プロジェクトの追加 – Backlog ヘルプセンター

Secrets Managerからシークレットの値を取得する

登録したBacklogのAPIキーGitHubのWebhookに設定したSecretを取得できるようにします。 基本的な実装はシークレットを登録した際にSecrets Managerの画面に表示されるサンプルコードを使用しました。

def get_secrets_manager_dict(secret_name: str) -> dict:
    """Secrets Managerからシークレットのセットを辞書型で取得する"""
    secrets_dict = {}
    if not secret_name:
        print('シークレットの名前未設定')
    else:
        session = boto3.session.Session()
        client = session.client(
            service_name='secretsmanager',
            region_name='対象のリージョン'
        )
        try:
            get_secret_value_response = client.get_secret_value(
                SecretId=secret_name
            )
        except ClientError as e:
            print('シークレット取得失敗:シークレットの名前={}'.format(secret_name))
            print(e.response['Error'])
        else:
            if 'SecretString' in get_secret_value_response:
                secret = get_secret_value_response['SecretString']
            else:
                secret = base64.b64decode(get_secret_value_response['SecretBinary'])
            secrets_dict = ast.literal_eval(secret)
    return secrets_dict

GitHubのWebhookから送られてくる内容を取得する処理を作る

GitHubのWebhookから送られてくる内容は以下サイトに説明があります。

まずは、GitHubから送られてきた情報が本当に設定したWebhookからなのを確認するためにHMAC認証します。 GitHubのWebhookに設定したSecretGitHubから送られてきた情報で認証を行います。

参考 : GitHubのWebhookでプルリクエストをマージした際にツイートできるようしてみた - Qiita

def is_correct_signature(signature: str, body: dict) -> bool:
    """GitHubから送られてきた情報をHMAC認証する."""
    if signature and body:
        # GitHubのWebhookに設定したSecretをSecrets Managerから取得する
        secret = get_secrets_manager_key_value('github/to/backlog', 'GITHUB_SECRET')
        if secret:
            secret_bytes = bytes(secret, 'utf-8')
            body_bytes = bytes(body, 'utf-8')
            # Secretから16進数ダイジェストを作成する
            signedBody = "sha1=" + hmac.new(secret_bytes, body_bytes, hashlib.sha1).hexdigest()
            return signature == signedBody
    else:
        return False

プッシュとプルリクではGitHubからくる情報が異なるというのとBacklogの課題に追加するコメントをちょっと変えるために処理を切り分けます。

操作 action pull_request commits
Pull requests o o x
Pushes x x o
def lambda_handler(event, context):
    # GitHubから送られてきた情報をHMAC認証する
    if is_correct_signature(event['headers']['x-hub-signature'], event['body']):
        body = json.loads(event['body'])
        # プッシュとプルリクを識別して処理を切り分ける
        if 'pull_request' in body and 'action' in body:
            # Pull requestsの場合
            add_pull_request_comment(body['action'], body['pull_request'], body['sender']['login'])
        elif 'commits' in body:
            # Pushesの場合
            add_push_comment(body['commits'])
        else:
            print('処理対象外のリクエストなので処理しない' + json.dumps(body))
    else:
        print('認証できないGitHubのsignatureが送られてきた')

コメントから課題キーを検索する

コメント追加する課題を決めるためにコミットコメントやプルリクのコメントから課題キーを検索します。 複数の課題キーがあればそれぞれの課題にコメントが追加できるようにリストに課題キーを入れて返却します。

def get_issue_key(message: str) -> list:
    """コメントから課題キーを検索する."""
    issue_key = []
    # 「[プロジェクトキー] + [-] + [数字の繰り返し]を全て抜出
    key_format = '{}-[\d]+'.format(os.environ.get('PROJECT_KEY'))
    match_list = re.findall(key_format, message)
    if match_list:
        # 抜き出した課題キーのリストから重複を削除する
        issue_key = list(set(match_list))
    else:
        print('コメントにBacklogの課題キーが設定されていない')
    return issue_key

プルリクエストはレビューアーに通知をつけたいので通知リストを作る

通知リストは次の流れで作成します。

  1. プルリクエストに設定されたレビューアーのGitHubユーザー名でSecrets ManagerからBacklogのメールアドレスを取得する
  2. APIで取得したBacklogのユーザー一覧からメールアドレスでBacklogのユーザーIDを探す
  3. 通知リストへ追加する
def create_notified_list(requested_reviewers: list, backlog_users: list) -> list:
    """レビューアーにBacklogの通知をつけるため、にコメント登録の通知を受け取るユーザーIDリストを作成する"""
    notified_list = []
    if requested_reviewers and backlog_users:

        for reviewer in requested_reviewers:
            # Secrets ManagerからBacklogのメールアドレスを取得する
            backlog_mail = get_secrets_manager_key_value('backlog/' + reviewer['login'], 'MAIL')

            if backlog_mail:
                for user in backlog_users:
                    # 取得したメールアドレスと同じメールアドレスのユーザーをBacklogユーザー一覧から探す
                    if backlog_mail == user['mailAddress']:
                        # ユーザーのIDをリストへ追加する
                        notified_list.append(user['id'])

    return notified_list

Backlogにコメントを追加する

def add_backlog_comment(api_key: str, issue_keys: list, comment: str, notified_list: list):
    """Backlogの課題にコメントを追加する."""
    if not api_key or not issue_keys:
        print('BacklogのAPIキーまたは課題キー未設定')
    else:
        params = {'apiKey': api_key}
        payload = {'content': comment}

        # コメント登録の通知を受け取るユーザーIDがある場合は設定する
        if notified_list:
            payload['notifiedUserId[]'] = notified_list

        # コメントにある課題すべてにコメントを追加する
        for issue_key in issue_keys:
            header = {'Content-Type': 'application/x-www-form-urlencoded'}
            api_path = urllib.parse.urljoin(os.environ.get('BACKLOG_ENDPOINT'), '/'.join(['issues', issue_key, 'comments']))
            result = requests.post(api_path, headers=header, params=params, data=payload)

できた!!

f:id:ponsuke_tarou:20201130165240p:plain

今回はシンプルにコメント追加をしました。 今後、BacklogのGitみたいに課題のステータス変更をしたり、レビューコメントも追加したりしたら楽しそうです!

コードの全体

ここ以降は、コード全部を張っているだけなので興味のある人だけ見てください。

import json, os
import hmac, hashlib
import requests, urllib
import boto3
import base64
import ast, re
from botocore.exceptions import ClientError


def get_secrets_manager_dict(secret_name: str) -> dict:
    """Secrets Managerからシークレットのセットを辞書型で取得する"""
    secrets_dict = {}
    if not secret_name:
        print('シークレットの名前未設定')
    else:
        session = boto3.session.Session()
        client = session.client(
            service_name='secretsmanager',
            region_name='対象のリージョン'
        )
        try:
            get_secret_value_response = client.get_secret_value(
                SecretId=secret_name
            )
        except ClientError as e:
            print('シークレット取得失敗:シークレットの名前={}'.format(secret_name))
            print(e.response['Error'])
        else:
            if 'SecretString' in get_secret_value_response:
                secret = get_secret_value_response['SecretString']
            else:
                secret = base64.b64decode(get_secret_value_response['SecretBinary'])
            secrets_dict = ast.literal_eval(secret)
    return secrets_dict


def get_secrets_manager_key_value(secret_name: str, secret_key: str) -> str:
    """AWS Secrets Managerからシークレットキーの値を取得する."""
    value = ''
    secrets_dict = get_secrets_manager_dict(secret_name)
    if secrets_dict:
        if secret_key in secrets_dict:
            # secrets_dictが設定されていてsecret_keyがキーとして存在する場合
            value = secrets_dict[secret_key]
        else:
            print('シークレットキーの値取得失敗:シークレットの名前={}、シークレットキー={}'.format(secret_name, secret_key))
    return value


def is_correct_signature(signature: str, body: dict) -> bool:
    """GitHubから送られてきた情報をHMAC認証する."""
    if signature and body:
        # GitHubのWebhookに設定したSecretをSecrets Managerから取得する
        secret = get_secrets_manager_key_value('github/to/backlog', 'GITHUB_SECRET')
        if secret:
            secret_bytes = bytes(secret, 'utf-8')
            body_bytes = bytes(body, 'utf-8')
            # Secretから16進数ダイジェストを作成する
            signedBody = "sha1=" + hmac.new(secret_bytes, body_bytes, hashlib.sha1).hexdigest()
            return signature == signedBody
    else:
        return False


def get_backlog_api_key(github_username: str) -> str:
    """GitHubのユーザ名から該当ユーザのBacklogのAPIキーを取得する."""
    api_key = ''
    if github_username:
        api_key = get_secrets_manager_key_value('backlog/' + github_username, 'APIKEY')
    else:
        print('GitHubユーザー名未設定')
    return api_key


def add_backlog_comment(api_key: str, issue_keys: list, comment: str, notified_list: list):
    """Backlogの課題にコメントを追加する."""
    if not api_key or not issue_keys:
        print('BacklogのAPIキーまたは課題キー未設定')
    else:
        params = {'apiKey': api_key}
        payload = {'content': comment}

        # コメント登録の通知を受け取るユーザーIDがある場合は設定する
        if notified_list:
            payload['notifiedUserId[]'] = notified_list

        # コメントにある課題すべてにコメントを追加する
        for issue_key in issue_keys:
            header = {'Content-Type': 'application/x-www-form-urlencoded'}
            api_path = urllib.parse.urljoin(os.environ.get('BACKLOG_ENDPOINT'), '/'.join(['issues', issue_key, 'comments']))
            result = requests.post(api_path, headers=header, params=params, data=payload)


def get_backlog_users(api_key: str) -> list:
    """Backlogユーザー一覧の取得"""
    users = []

    if not api_key:
        print('BacklogのAPIキー未設定')
    else:
        # ユーザーの取得対象はプロジェクト内に設定する
        api_path = urllib.parse.urljoin(os.environ.get('BACKLOG_ENDPOINT'), '/'.join(['projects', os.environ.get('PROJECT_KEY'), 'users']))
        api_result = requests.get(api_path, params={'apiKey': api_key}).json()

        if type(api_result) == list and api_result:
            # Backlogのユーザー一覧を取得する
            users = api_result
        else:
            print('Backlogプロジェクトのユーザー一覧取得失敗:{}'.format(json.dumps(api_result)))

    return users


def create_notified_list(requested_reviewers: list, backlog_users: list) -> list:
    """レビューアーにBacklogの通知をつけるため、にコメント登録の通知を受け取るユーザーIDリストを作成する"""
    notified_list = []
    if requested_reviewers and backlog_users:

        for reviewer in requested_reviewers:
            # Secrets ManagerからBacklogのメールアドレスを取得する
            backlog_mail = get_secrets_manager_key_value('backlog/' + reviewer['login'], 'MAIL')

            if backlog_mail:
                for user in backlog_users:
                    # 取得したメールアドレスと同じメールアドレスのユーザーをBacklogユーザー一覧から探す
                    if backlog_mail == user['mailAddress']:
                        # ユーザーのIDをリストへ追加する
                        notified_list.append(user['id'])

    return notified_list


def get_issue_key(message: str) -> list:
    """コメントから課題キーを検索する."""
    issue_key = []
    # 「[プロジェクトキー] + [-] + [数字の繰り返し]を全て抜出
    key_format = '{}-[\d]+'.format(os.environ.get('PROJECT_KEY'))
    match_list = re.findall(key_format, message)
    if match_list:
        # 抜き出した課題キーのリストから重複を削除する
        issue_key = list(set(match_list))
    else:
        print('コメントにBacklogの課題キーが設定されていない')
    return issue_key


def add_push_comment(commits: list):
    print('プッシュの情報をBacklogの課題へコメント追加する')
    print(json.dumps(commits))
    # プッシュに含まれるコミットを1つずつ処理する
    for commit in commits:
        issue_key = []
        if 'message' in commit:
            issue_keys = get_issue_key(commit['message'])
        if issue_keys:
            backlog_api_key = get_backlog_api_key(commit['committer']['username'])
            if backlog_api_key:
                comment_format = '- URL:{}\n{}'
                comment = comment_format.format(commit['url'], commit['message'])
                add_backlog_comment(backlog_api_key, issue_keys, comment, [])


def add_pull_request_comment(action: str, pull_request: dict, username: str):
    print('プルリクエストの情報をBacklogの課題へコメント追加する\nアクション:{}\nマージ:{}'.format(action, str(pull_request['merged'])))
    print(json.dumps(pull_request))
    # プルリクのタイトルとコメントから課題キーを取得する
    issue_keys = get_issue_key(pull_request['title'] + pull_request['body'])
    if issue_keys:
        # BacklogのAPIキーを取得する
        backlog_api_key = get_backlog_api_key(username)

        if backlog_api_key:
            notified_list = []
            if 'requested_reviewers' in pull_request and pull_request['requested_reviewers']:
                # Backlogのユーザー一覧を取得する
                backlog_users = get_backlog_users(backlog_api_key)
                if backlog_users:
                    # レビューアーが設定されている場合は追加するコメント用の通知リストを作成する
                    notified_list = create_notified_list(pull_request['requested_reviewers'], backlog_users)

            # プルリクのアクションによってコメントを作成する
            comment = ''
            if action == 'opened' or action == 'reopened':
                comment = 'プルリクエストが作成されました'
            elif action == 'closed':
                if pull_request['merged']:
                    comment = 'プルリクエストがマージされました'
                else:
                    comment = 'プルリクエストが却下されました'
            else:
                comment = 'プルリクエストが変更されました'
            comment += '\n\n- URL:{}'.format(pull_request['html_url'])

            add_backlog_comment(backlog_api_key, issue_keys, comment, notified_list)


def lambda_handler(event, context):
    # GitHubから送られてきた情報をHMAC認証する
    if is_correct_signature(event['headers']['x-hub-signature'], event['body']):
        body = json.loads(event['body'])
        # プッシュとプルリクを識別して処理を切り分ける
        if 'pull_request' in body and 'action' in body:
            # Pull requestsの場合
            add_pull_request_comment(body['action'], body['pull_request'], body['sender']['login'])
        elif 'commits' in body:
            # Pushesの場合
            add_push_comment(body['commits'])
        else:
            print('処理対象外のリクエストなので処理しない' + json.dumps(body))
    else:
        print('認証できないGitHubのsignatureが送られてきた')

Kintoneの開発環境を作成する

Kintoneの開発環境って何?

kintone API を使った開発用に1年間Kintoneが使えるようになります。

kintone 開発者ライセンスは、kintoneのアプリケーション開発を目的として、ご利用いただける開発環境です。本運用のご利用はできません。

kintone 開発者ライセンス(開発環境) – cybozu developer network

開発環境を作成する

cybozu developer networkにアカウントを作成する

  1. cybozu developer networkを表示する
  2. 右上の[サインイン]ボタンでポップアップを表示する
  3. [cybozu developer network を初めてご利用の方: アカウント登録]リンクから登録用のポップアップを表示する
  4. 入力項目を入力して[アカウント登録]で仮登録する
  5. 入力したメールで件名が「cybozu developer networkへようこそ」のメールに書かれたURLからサイトを表示する
  6. パスワードを設定してログインする

kintone開発者ライセンスを取得する

  1. ログイン後の画面で[kintone開発者ライセンスを取得]ボタンでページを表示する
  2. 内容を確認した後で[開発者ライセンスを申し込む]ボタンで申込ページを表示する
    • f:id:ponsuke_tarou:20201201174417p:plain
  3. 申込みフォームを入力して[申込み]ボタンで申し込む
    • f:id:ponsuke_tarou:20201201174524p:plain
  4. 申し込み完了メッセージは画面上部に表示される
    • f:id:ponsuke_tarou:20201201174755p:plain
      画面が上部に移動しのでエラーがあったのかと思ったけど大丈夫だった。
  5. メールがやってくるのをしばし待つ > メールが来たら書いてある[アクセスURL][ログイン名][パスワード]でKintoneにログインする
    • f:id:ponsuke_tarou:20201201192409p:plain
      開発環境のKintoneが使えるようになった!

使ってみる

パスワード認証を使ってスペース情報取得してみる

  1. [Kintone] > [スペース]の右にある[+]ボタン > [スペースを作成]からポップアップを表示
  2. [はじめから作る] > [基本設定]タブの内容を入力([参加メンバー]タブは誰もいないので設定しない)
  3. [保存]ボタンでスペースを作成する
  4. 「ログイン名:パスワード」をbase64エンコードする
  5. 以下を実行してスペース情報を取得する
    • crul + -H X-Cybozu-Authorization:{base64エンコードしたもの} + https://{サブドメイン名}.cybozu.com/k/v1/space.json?id={スペースのID}
コード 意味 参考
-H X-Cybozu-Authorization:{base64エンコードしたもの} ユーザ認証用のリクエストヘッダ kintone REST APIの共通仕様 – cybozu developer network
https://{サブドメイン名}.cybozu.com/k/v1/space.json?id={スペースのID} スペース情報の取得用のURIとリクエストパラメータ スペース情報の取得 – cybozu developer network
# 最後の改行を出力しない(-n)ようにしてbase64エンコードする
% echo -n '{ログイン名}:{パスワード}' | base64
xxxxx==

# スペース情報を取得する
% curl -H 'X-Cybozu-Authorization:xxxxx==' https://hoge.cybozu.com/k/v1/space.json?id=1
{"id":"1","name":"はじめてのスペース","defaultThread":"1","isPrivate":true,"creator":{"code":"...

APIトークン認証を使ってアプリ情報取得してみる

  1. スペースのページを表示する
  2. [アプリ]の右にある[+]ボタンで作成画面を表示する
  3. [初めから作成] > 手頃にパーツを配置する > [アプリを公開] > [OK]ボタンでアプリを作成する
    • f:id:ponsuke_tarou:20201201210935p:plain
      手頃にこんな感じで作りました。
  4. APIトークンを生成するを参考にトークンを作成する
  5. 以下を実行してスペース情報を取得する
    • crul + -H X-Cybozu-API-Token:{APIトークン} + https://{サブドメイン名}.cybozu.com/k/v1/app/form/fields.json?app={アプリのID}\&lang=ja
コード 意味 参考
-H X-Cybozu-API-Token:{APIトークン} APIトークン認証用のリクエストヘッダ kintone REST APIの共通仕様 – cybozu developer network
https://{サブドメイン名}.cybozu.com/k/v1/app/form/fields.json?app={アプリのID}\&lang=ja フィールドの一覧を取得用のURIとリクエストパラメータ フォームの設定の取得 – cybozu developer network
% curl -H X-Cybozu-API-Token:XxxxXx https://hoge.cybozu.com/k/v1/app/form/fields.json?app={アプリのID}\&lang=ja
{"revision":"4","properties":{"カテゴリー":{"type":"CATEGORY","code":"カテゴリー","label":"カテゴリー","enabled":false},"テーブル":{"type":"SUBTABLE","code":"テーブル","noLabel":false,"label":"テーブル","fields":{"数値":{"type":"NUMBER","code":"数値","label":"数値","noLabel":false,...

f:id:ponsuke_tarou:20201201213930j:plain
明治通りで拾ったかりん、蜂蜜でつけてみた

万二郎岳と万三郎岳 in 天城山

先週は那須で登山をしました。

ponsuke-tarou.hatenablog.com

万二郎岳

f:id:ponsuke_tarou:20201124203809j:plain
天城高原ゴルフ場の横にあるハイカー専用駐車場に車を停めました。
この駐車場はおトイレだけでなく、登山靴の洗い場にブラシまで設置された至れり尽くせりな駐車場でした。 とてもありがたいです。
f:id:ponsuke_tarou:20201124202056j:plain
今回は、シャクナゲコースを回ります。
登山道は駐車場のすぐ横にあります。 バス停が登山道入り口横にあってとてもわかりやすかったです。
f:id:ponsuke_tarou:20201124202141j:plain
なだらかな道が続いてとても歩きやすいです。
道は1つなので迷いにくいのですが、水のない川や落ち葉の積もった平地などがあって「あれ?」っと思うところもあります。 登山道から外れないようにロープがしっかり張ってあるのですが、ときどきロープのどっち側が登山道か迷うことがありました。 気につけられたピンクや青のテープも参考にしながら気をつけて歩きます。
f:id:ponsuke_tarou:20201124202227j:plain
ついつい足元ばかり見てしまいますが、しっかり顔をあげて周りを見ながら歩きました。
山頂に近づいていくにつれてあせびの木がたくさん!あせびが咲くのは早春らしく、きっとまだ雪が残っているだろうからこられそうにはないけどきっとすごく綺麗なんだろうなぁと想像しながら進みました。 f:id:ponsuke_tarou:20201124202247j:plain

万三郎岳

f:id:ponsuke_tarou:20201124202330j:plain 万三郎岳が近づいてくるとコースの名前にもなっているアマギシャクナゲが登山道に増えてきます。 標識によると5~6月に見頃を迎えるそうなので次はぜひ咲いているときに来てみたいです。 f:id:ponsuke_tarou:20201124202432j:plain f:id:ponsuke_tarou:20201124202455j:plain

裸の木がいっぱい!です。木についているフダをみるとヒメシャラとリョウブなのですが・・・見分けがつきません。 すっかり落葉したこの時期に「リョウブ」「ヒメシャラ」「サルスベリ」を木の幹と樹形だけで見分けられるようになりたいです。 botanica-media.jp

f:id:ponsuke_tarou:20201124202537j:plain
登山道にはたくさんのヒメシャラとリョウブがたくさん
f:id:ponsuke_tarou:20201124203250j:plain f:id:ponsuke_tarou:20201124203633j:plain 基本的にシャクナゲコースは初心者向けらしいのですが山頂から戻る道はちょっと足場が不安定なところが多いです。
f:id:ponsuke_tarou:20201124203705j:plain
途中でお年寄りのご夫婦が「きつい!」と休憩しておりました。確かに何度かこけました。
岩場とまではいかないけれど石の多い場所や木の階段が崩れたところが続いているので気を張って進みます。 子供や若い人はぽんぽん進めるけれど、中年に差し掛かっている身としては膝にこないよう気を使います。 駐車場に戻って時間を見ると全体で5時間ちょっとの道のりでした。 私は歩くのが遅いので登山に慣れている人だときっと4時間くらいで回るのかもしれませんね。

登山の後はやっぱり温泉!

温泉だ!と検索して出てきたのは伊豆市冷川にある「源泉湯治の宿 ごぜんの湯」。早速向かってみると・・・休業のふだがかかっていた・・・ onsen.surugabank.co.jp というわけで道の駅伊東マリンタウンで温泉に入って帰りました。 ito-marinetown.co.jp

天城山(シャクナゲコース)| 山ガールのための山歩きガイド コースガイド 女性のための登山情報サイト 山ガールネットで紹介されている日帰り温泉「東海館」というところを次は狙うべし!