2022年3月31日木曜日

Sorbet 超入門

Sorbet 超入門

概要

Stripe が作成した型チェックツール Sorbet (しゃーべっと)を使ってみました

過去に紹介した記事では typeprof + rbs + steep を使っていますが Sorbet はこれだけで使えます

環境

  • macOS 11.6.5
  • Ruby 2.6.5
  • Sorbet 0.5.9798

事前準備

Ruby3 でやるといろいろとエラーになるので Ruby2.6.5 で試しました

rbenv などを使って今回用にインストールしてあげましょう

  • rbenv install 2.6.5
  • cd /path/to/try_sorbet
  • rbenv local 2.6.6

インストール

  • bundle init
  • vim Gemfile
gem "sorbet"
gem "sorbet-runtime"
  • bundle install --path vendor/bundle

一応ポイントですが空の状態からやったほうがいいです既存の gem がある状態でやるといろいろとエラーが出ました

とりあえず使ってみる

srb tc コマンドで型チェックできます

  • bundle exec srb tc -e 'puts "Hello, world!"'`

プロジェクト初期化

  • bundle exec srb init

とりあえず Yes で初期化します
いろいろとログが流れますが待ちましょう
sorbet/ ディレクトリが作成されれば OK です

テスト用スクリプト作成

公式のサンプルをそのまま持ってきます

  • vim app.rb
# typed: true
require 'sorbet-runtime'

class A
  extend T::Sig

  sig {params(x: Integer).returns(String)}
  def bar(x)
    x.to_s
  end
end

def main
  A.new.barr(91)   # error: Typo!
  A.new.bar("91")  # error: Type mismatch!
end

型チェック実行

srb tc で実行できます

  • bundle exec srb tc
app.rb:14: Method barr does not exist on A https://srb.help/7003
    14 |  A.new.barr(91)   # error: Typo!
                ^^^^
  Got A originating from:
    app.rb:14:
    14 |  A.new.barr(91)   # error: Typo!
          ^^^^^
  Autocorrect: Use `-a` to autocorrect
    app.rb:14: Replace with bar
    14 |  A.new.barr(91)   # error: Typo!
                ^^^^

app.rb:15: Expected Integer but found String("91") for argument x https://srb.help/7002
    15 |  A.new.bar("91")  # error: Type mismatch!
                    ^^^^
  Expected Integer for argument x of method A#bar:
    app.rb:7:
     7 |  sig {params(x: Integer).returns(String)}
                      ^
  Got String("91") originating from:
    app.rb:15:
    15 |  A.new.bar("91")  # error: Type mismatch!
                    ^^^^
Errors: 2

タイポと型エラーが表示できるのが確認できます

書き方の説明

# typed: true

require の前に必須になります
もし既存の Ruby スクリプトがあれば init 時に自動で挿入してくれます

extend T::Sig

クラス内で使う場合には必須になります
これで sig 構文が使えるようになります

sig {params(x: Integer).returns(String)}

これがいわゆるタイプアノテーションになります
メソッドチェーンで記載します
各種パラメータの型と返り値の型を指定します
プリミティブ型以外にも独自で定義したクラスも指定できます

最後に

まだまだ開発中なのでもう少し成熟するのを待ったほうがいいかもしれません

参考サイト

2022年3月30日水曜日

ソースコードインストールした Gitlab でバックアップを取得する方法

ソースコードインストールした Gitlab でバックアップを取得する方法

概要

前回 Gitlab14.9 をソースコードインストールしました
今回はソースコードインストールした Gitlab 上でバックアップタスクを実行する方法を紹介します

環境

  • Ubuntu 18.04
  • Gitlab 14-9-ee branch

gitaly-backup コマンドのパスを指定

gitaly_backup_path がデフォルトだと空になっておりバックアップがエラーになるので設定します

  • cd /home/git/gitlab
  • sudo -u git -H editor config/gitlab.yml
  backup:
    path: "tmp/backups"
    gitaly_backup_path: /home/git/gitaly/_build/bin/gitaly-backup

バックアップ実行

  • sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production

バックアップファイル確認

/home/git/gitlab/tmp/backups/ 配下に tar ファイルがあることを確認してください

おまけ: SKIP オプションの指定方法

  • sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production SKIP=tar

おまけ: s3にアップロードする設定

  • sudo -u git -H editor config/gitlab.yml
  backup:
    path: "tmp/backups"
    gitaly_backup_path: /home/git/gitaly/_build/bin/gitaly-backup
    keep_time: 1
    upload:
      connection:
        provider: AWS
        region: us-east-2
        aws_access_key_id: AKIxxx
        aws_secret_access_key: 'xxx'
      remote_directory: 'bucket_name'

参考サイト

2022年3月29日火曜日

Gitlab 14.9 をソースコードからインストールしてみた

Gitlab 14.9 をソースコードからインストールしてみた

概要

過去に11.8をソースコードからインストールしました
今回は最新版のGitlab14.9をソースコードからインストールしてみました

以下は主に root ユーザで作業を進めます

環境

  • Ubuntu 18.04
  • Gitlab 14-9-ee branch
  • Ruby 2.7.4
  • Go 1.16.10
  • Nodejs 12.22.11
    • yarn 1.22.18
  • Postgres 10.19
  • Redis 4.0.9
  • git 2.33.1.gl3

依存パッケージのインストール

  • apt -y update
sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev \
  libreadline-dev libncurses5-dev libffi-dev curl openssh-server libxml2-dev libxslt-dev \
  libcurl4-openssl-dev libicu-dev logrotate rsync python-docutils pkg-config cmake runit-systemd

git の最新版をインストール

  • sudo apt-get install -y libcurl4-openssl-dev libexpat1-dev gettext libz-dev libssl-dev libpcre2-dev build-essential git-core
  • git clone https://gitlab.com/gitlab-org/gitaly.git -b 14-9-stable /tmp/gitaly
  • cd /tmp/gitaly
  • sudo make git GIT_PREFIX=/usr/local
  • sudo apt remove -y git-core
  • sudo apt autoremove

一度ログアウトして git --version でバージョンが表示されれば OK です

GraphicsMagick のインストール

  • sudo apt-get install -y graphicsmagick

Postfix のインストール

  • sudo apt-get install -y postfix

host 名は DNS に設定する A レコードの値を入力します

Exiftool のインストール

  • sudo apt-get install -y libimage-exiftool-perl

Ruby のインストール

  • mkdir /tmp/ruby && cd /tmp/ruby
  • curl --remote-name --location --progress-bar "https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.4.tar.gz"
  • echo '3043099089608859fc8cce7f9fdccaa1f53a462457e3838ec3b25a7d609fbc5b ruby-2.7.4.tar.gz' | sha256sum -c - && tar xzf ruby-2.7.4.tar.gz
  • cd ruby-2.7.4
  • ./configure --disable-install-rdoc --enable-shared
  • make
  • sudo make install

一度ログアウトして ruby -v でバージョンが表示されれば OK です

Go のインストール

  • sudo rm -rf /usr/local/go
  • curl --remote-name --location --progress-bar "https://go.dev/dl/go1.16.10.linux-amd64.tar.gz"
  • echo '414cd18ce1d193769b9e97d2401ad718755ab47816e13b2a1cde203d263b55cf go1.16.10.linux-amd64.tar.gz' | shasum -a256 -c - && \ sudo tar -C /usr/local -xzf go1.16.10.linux-amd64.tar.gz
  • sudo ln -sf /usr/local/go/bin/{go,gofmt} /usr/local/bin/
  • rm go1.16.10.linux-amd64.tar.gz

一度ログアウトして go version でバージョンが表示されれば OK です

Nodejs と yarn のインストール

  • curl --location "https://deb.nodesource.com/setup_14.x" | sudo bash -
  • sudo apt-get install -y nodejs
  • npm install --global yarn

node -vyarn -v でバージョンが表示されれば OK です

git ユーザの作成

  • sudo adduser --disabled-login --gecos 'GitLab' git
  • usermod -aG sudo git
  • passwd git

Postgres のインストールと設定

  • wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
  • sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
  • sudo apt update
  • sudo apt -y install postgresql-12 postgresql-client-12 libpq-dev
  • sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;"
  • sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
  • sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS btree_gist;"
  • sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;"
  • sudo -u git -H psql -d gitlabhq_production

SQL プロンプトになったら pg_trgm エクステンションが有効になっているか確認します

  • SELECT true AS enabled FROM pg_available_extensions WHERE name = 'pg_trgm' AND installed_version IS NOT NULL;
  • SELECT true AS enabled FROM pg_available_extensions WHERE name = 'btree_gist' AND installed_version IS NOT NULL;

Redis のインストール

  • sudo apt-get install redis-server
  • sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
  • sudo sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf
  • echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
  • echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf
  • sudo usermod -aG redis git
  • sudo sed -i -e 's/^daemonize yes$/daemonize no/' -e 's/^supervised no$/supervised systemd/' -e 's/^pidfile/# pidfile/' /etc/redis/redis.conf
  • sudo chown redis:redis /etc/redis/redis.conf
  • sudo mkdir -p /etc/systemd/system/redis-server.service.d
sudo tee /etc/systemd/system/redis-server.service.d/10fix_type.conf <<EOF
[Service]
Type=notify
PIDFile=
EOF
  • sudo systemctl daemon-reload
  • sudo systemctl restart redis-server.service

Gitlab インストールと設定

  • cd /home/git
  • sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab.git -b 14-9-stable-ee gitlab
  • cd /home/git/gitlab
  • sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml
  • sudo -u git -H editor config/gitlab.yml
production: &base
  git:
    bin_path: /usr/local/bin/git
  • sudo -u git -H cp config/secrets.yml.example config/secrets.yml
  • sudo -u git -H chmod 0600 config/secrets.yml
  • sudo chown -R git log/
  • sudo chown -R git tmp/
  • sudo chmod -R u+rwX,go-w log/
  • sudo chmod -R u+rwX tmp/
  • sudo chmod -R u+rwX tmp/pids/
  • sudo chmod -R u+rwX tmp/sockets/
  • sudo -u git -H mkdir -p public/uploads/
  • sudo chmod 0700 public/uploads
  • sudo chmod -R u+rwX builds/
  • sudo chmod -R u+rwX shared/artifacts/
  • sudo chmod -R ug+rwX shared/pages/
  • sudo -u git -H cp config/puma.rb.example config/puma.rb
  • sudo -u git -H git config --global gc.auto 0
  • sudo -u git -H git config --global repack.writeBitmaps true
  • sudo -u git -H git config --global receive.advertisePushOptions true
  • sudo -u git -H git config --global core.fsyncObjectFiles true
  • sudo -u git -H cp config/resque.yml.example config/resque.yml
  • sudo -u git -H cp config/cable.yml.example config/cable.yml

DB の設定

  • sudo -u git cp config/database.yml.postgresql config/database.yml
  • sudo -u git -H chmod o-rwx config/database.yml
  • sudo -u git -H editor config/database.yml
production:
  main:
    adapter: postgresql
    encoding: unicode
    database: gitlabhq_production
    # username: git
    # password: "secure password"
    # host: localhost

Gem のインストール

  • sudo -u git -H bundle config set --local deployment 'true'
  • sudo -u git -H bundle config set --local without 'test mysql aws kerberos'
  • sudo -u git -H bundle install

rake -T でタスクの一覧を表示したり他のタスクを実行したりするので development はインストールするようにしています

Gitlab Shell のインストール

  • sudo -u git -H bundle exec rake gitlab:shell:install RAILS_ENV=production

GitLab Workhorse のインストール

  • sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production

GitLab Elasticsearch indexer のインストール

  • sudo -u git -H bundle exec rake "gitlab:indexer:install[/home/git/gitlab-elasticsearch-indexer]" RAILS_ENV=production

Gitlab Pages のインストール

今回は必須ではないため省略します

Gitaly のインストールと起動

  • cd /home/git/gitlab
  • sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
  • sudo chmod 0700 /home/git/gitlab/tmp/sockets/private
  • sudo chown git /home/git/gitlab/tmp/sockets/private

systemd への登録

  • cd /home/git/gitlab
  • sudo mkdir -p /usr/local/lib/systemd/system
  • sudo cp lib/support/systemd/* /usr/local/lib/systemd/system/
  • sudo systemctl daemon-reload

ログローテションの設定

  • sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab

gitaly 起動

  • sudo systemctl start gitlab-gitaly.service

データベースのマイグレート

  • sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production force=yes

これまでのアプリの状態を確認する

  • sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production

GetText PO files の作成

  • sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production

アセットファイルの作成

  • sudo -u git -H yarn install --production --pure-lockfile
  • sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production NODE_ENV=production

Gitlab の起動

  • sudo systemctl start gitlab.target

ログは /home/git/gitlab/log/application.log にあります

Nginx のインストールと設定、起動

  • sudo apt-get install -y nginx
  • sudo cp lib/support/nginx/gitlab /etc/nginx/sites-available/gitlab
  • sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab
  • sudo rm -f /etc/nginx/sites-enabled/default
  • sudo nginx -t
  • sudo systemctl restart nginx.service

動作確認

  • sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
http://192.168.100.1
にアクセスして root のパスワードを設定してログインできることを書くにしましょう

最後に

基本は公式の手順通りに実行すれば OK です
少し違っていたのは以下の通りです

  • git ユーザの sudo 化
  • redis のバージョンが4系になっていた

ドメインでのアクセスやSSLの設定は飛ばしています

参考サイト

2022年3月28日月曜日

Ruby のアンパサンド (Proc Symbol) を使う

Ruby のアンパサンド (Proc Symbol) を使う

概要

Ruby のアンパサンドにはいろいろな使い方があります

例えばループなどの処理でブロックを取る場合にブロック引数の代わりにアンパサンドを使って Proc を取ることができます
状況によってはある程度記述を省略することができるテクニックです

環境

  • macOS 11.6.5
  • Ruby 3.0.3p157

演算子としてのアンパサンド

[1, 2] & [1, 2, 3]
=> [1, 2]

ブロック展開

proc = Proc.new {|i| p i}
10.times(&proc)

Proc オブジェクトを受け取る

各要素文字列にする

10.times.map(&:to_s)

これは以下と同じ

10.times.map {|i| i.to_s}
=> ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]

要素を表示する

10.times(&method(:p))

これは以下と同じ

10.times{|i| p i}

結果

0
1
2
3
4
5
6
7
8
9
=> 10

コロンのあるなしは

  • コロンがあるのは to_proc 可能な場合 (プライベートメソッドを呼び出す場合)
  • コロンがないのは to_proc できない場合 (他のクラスのメソッドを呼び出す場合)

おまけ: ぼっち演算子

["a","b",nil].each {|i| i.upcase}

これはエラーになるが以下はエラーにならない

["a","b",nil].each {|i| i&.upcase}

参考サイト

2022年3月25日金曜日

SwiftUI を試してみたのでメモ

SwiftUI を試してみたのでメモ

概要

SwiftUI のチュートリアルの1章を試してみたの成果物と感じたことをメモしておきます

環境

  • macOS 11.6.5
  • Xcode 13.1

感想

  • 開発は Vue や React のように宣言的に行える (状態が変わると自動で描画し直すなど)
  • コンポーネントごとに SwiftUI ファイルを作成できる
  • Stack を使いこなせるといい UI ができそう
  • 既存の UIKit から SwiftUI へのコンバートは頑張ればできるみたい
  • ライブプレビューは便利だがある程度スペックのあるマシンでないと重くなる

以下成果物です

ContentView.swift

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            MapView().ignoresSafeArea(edges: .top).frame(height: 300)
            CircleImage().offset(y: -130).padding(.bottom, -130)
            VStack(alignment: .leading) {
                Text("現在地").font(.title)
                HStack {
                    Text("詳細1")
                    Spacer()
                    Text("詳細2")
                }.font(.subheadline).foregroundColor(.secondary)
                Divider()
                Text("現在地について").font(.title2)
                Text("説明")
            }.padding()
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

CircleImage.swift

import SwiftUI

struct CircleImage: View {
    var body: some View {
        Image("test").resizable().frame(width: 150, height: 150).clipShape(Circle()).overlay(Circle().stroke(Color.white, lineWidth: 4)).shadow(radius: 7)
    }
}

struct CircleImage_Previews: PreviewProvider {
    static var previews: some View {
        CircleImage()
    }
}

MapView.swift

import SwiftUI
import MapKit

struct MapView: View {
    @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 35.6834875, longitude: 139.7608643), span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
    
    var body: some View {
        Map(coordinateRegion: $region)
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}

2022年3月24日木曜日

Gitlab と連携する AzureAD にユーザを追加する方法

Gitlab と連携する AzureAD にユーザを追加する方法

概要

過去に Gitlab + AzureAD の連携方法を紹介しました
今回はユーザを AzureAD に追加する方法を紹介します

環境

  • Gitlab 14.5.4

1. ユーザの追加

ホーム -> 既定のディレクトリ -> ユーザー -> 新しいユーザー

で追加します
必須パラメータはすべて埋めましょう

2. エンタープライズアプリケーションにユーザを登録する

ホーム -> 既定のディレクトリ -> エンタープライズアプリケーション -> Azure AD SAML Toolkit -> ユーザーとグループ -> 割り当ての追加

先程追加したユーザを選択しましょう

3. ユーザのメールアドレスを設定する

登録したユーザを選択してメールアドレスを設定しましょう
Gitlab と連携する場合はメールアドレスの設定が必須になります

注意点

初回ログイン時にはパスワードを変更するように求められるので変更しましょう

2022年3月23日水曜日

AWS CLI でプロキシを設定する方法

AWS CLI でプロキシを設定する方法

概要

環境変数を使います

環境

  • Ubuntu 18.04
  • aws cli 2.4.23

サンプルコマンド

HTTPS_PROXY=http://192.168.100.1:3128 HTTP_PROXY=http://192.168.100.1:3128 aws s3 ls --endpoint-url https://your.s3.host.com

ちなみに --endpoint-url オプションはなぜか .aws/config ファイルに記載できないので必ずオプションで指定する必要があります

https://github.com/aws/aws-cli/issues/1270

2022年3月22日火曜日

This operation can fail if the version of the OS on the device is incompatible with the installed version of Xcode.

This operation can fail if the version of the OS on the device is incompatible with the installed version of Xcode.

概要

実機デバック時に発生したので対応しました

環境

  • Xcode 13.2.1
  • iPhone 15.4

原因

Xcode 13.2.1 が iPhone15.4 でのビルドに対応していないのが原因です

DeviceSupport をインストールしてあげます

方法

  • wget https://github.com/filsv/iPhoneOSDeviceSupport/raw/master/15.4.zip
  • unzip 15.4.zip
  • sudo mv 15.4 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/

これで Xcode を再起動すれば OK です

参考サイト

2022年3月18日金曜日

docker-compose で起動した Gitlab にリストアしてみた

docker-compose で起動した Gitlab にリストアしてみた

概要

過去に helm chart で作成した gitlab にリストアしてみました
今回は docker-compose で構築した gitlab にリストアしてみます

環境

  • Gitlab 14.6.6-ee

バックアップの作成

どの環境でも OK です
docker-compose の場合は以下のように作成します
バックアップファイルはコンテナ内に作成されるので何かしらの方法でリストア先のgitlabに持っていきます

  • docker-compose exec gitlab gitlab-backup create
  • docker-compose exec gitlab gitlab-ctl backup-etc

リストア先のGitlabの作成

docker-compose で構築しましょう

puma と sidekiq の停止

リストア先のgitlabで行います

  • docker-compose exec gitlab gitlab-ctl stop puma
  • docker-compose exec gitlab gitlab-ctl stop sidekiq

secret を展開する

まずはシークレットから展開します
今回のシークレット関連のバックアップファイルは「gitlab_config_1234567890_2022_03_16.tar」とします
このファイルをリストア先のコンテナに配置しましょう
今回は /secret 配下に配置しました
あとは tar で展開します

  • docker-compose exec gitlab tar xvf /secret/gitlab_config_1234567890_2022_03_16.tar

展開されると /etc/gitlab 配下に gitlab.rb や gitlab-secrets.json などが上書き展開されます

データファイルの配置

次にデータファイルの展開を行います
今回のデータファイルバックアップファイルは「1234567890_2022_03_16_14.6.6-ee_gitlab_backup.tar」とします
このファイルを /var/opt/gitlab/backups に配置します
また配置したら tar ファイルの権限を git ユーザに変更しましょう

  • docker-compose exec gitlab chown git:git /var/opt/gitlab/backups/1234567890_2022_03_16_14.6.6-ee_gitlab_backup.tar
  • docker-compose exec gitlab ls -ltr /var/opt/gitlab/backups
total 1700
-rw------- 1 git git 389120 Mar 16 01:23 1234567890_2022_03_16_14.6.6-ee_gitlab_backup.tar

リストアの実行

あとはリストアを実行します

  • docker-compose exec gitlab gitlab-backup restore BACKUP=1234567890_2022_03_16_14.6.6-ee

BACKUP 変数で指定する値は tar ファイルの _gitlab_backup.tar より前のファイル名になります

いくつか質問が聞かれますが基本的には yes で OK です
「Restore task is done.」が表示されればリストア完了です

Gitlabの再起動

再起動をして再度 docker-compose に記載されたパラメータを反映します

  • docker-compose restart gitlab

動作確認

  • docker-compose exec gitlab gitlab-rake gitlab:check SANITIZE=true

でエラーにならないことを確認しましょう
またブラウザで gitlab にアクセスしてデータがリストアされていることを確認しましょう

最後に

流れとしては簡単です
ポイントとしては

  • リストア先のバージョンとバックアップ元のGitlabのバージョンを合わせる
  • コンテナの適切な箇所にシークレットとバックアップファイルを配置する
  • リストア先のGitlabは新規のGitlabにする

あたりかなと思います
また ce バージョンと ee バージョンでも挙動が異なる可能性があるので ce or ee も合わせたほうが無難かなと思います

参考サイト

2022年3月17日木曜日

(fog) An error occurred while installing ovirt-engine-sdk

(fog) An error occurred while installing ovirt-engine-sdk

概要

Ruby の fog をインストール中にタイトルのエラーが発生しました
パッケージが足りないので追加でインストールしてあげましょう

環境

  • Ubuntu 18.04
  • Ruby 3.0.0p0
  • fog 2.2.0

エラー詳細

An error occurred while installing ovirt-engine-sdk (4.4.1), and Bundler cannot continue.
Make sure that `gem install ovirt-engine-sdk -v '4.4.1' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  fog was resolved to 2.2.0, which depends on
    fog-ovirt was resolved to 2.0.1, which depends on
      ovirt-engine-sdk

対処方法

  • apt -y install build-essential libcurl4-openssl-dev

Ubuntu 環境の場合は上記で OK です
それ以外は各種環境に合わせた上記同様のパッケージをインストールしてください

参考サイト

2022年3月16日水曜日

Python でリストや辞書のタイピング入門

Python でリストや辞書のタイピング入門

概要

前回は基本的なタイピングに入門しました
今回はリストや辞書などの少し複雑なタイピングを試してみました
記事内では List, Dict, TypedDict, Optional, Any, Literal, TypeVar を使ったサンプルコードとコメントをベースに紹介します

環境

  • macOS 11.6.4
  • Python 3.10.2

List や Dict を使ったタイピングのサンプルコード

"""リストや辞書のタイピングをテストするモジュール."""
from typing import List, Dict, Union


class Profile():
    """プロファイルを管理するクラス."""

    def __init__(self):
        """お気に入り(リスト)とステータス(辞書)を初期化.

        値は文字列と数値を許容するためUnionで定義する
        """
        self.favorites: List[Union[str, int]] = []
        self.status: Dict[str: Union[str, int]] = {}

    def add_f(self, favorite: Union[str, int]) -> None:
        """お気に入りを1つ追加."""
        self.favorites.append(favorite)

    def add_s(self, key: str, value: Union[str, int]) -> None:
        """ステータスを1つ追加."""
        self.status[key] = value

    def show(self) -> None:
        """お気に入りとステータスを表示する."""
        print(self.favorites)
        print(self.status)


if __name__ == '__main__':
    p = Profile()
    p.add_f("game")
    p.add_f(1)
    p.add_s("name", "hawksnowlog")
    p.add_s("age", 10)
    p.show()

mypy を使ってエラーが出ないことを確認しましょう
List や Dict の値で文字列以外も扱いたい場合は Union を使うことで複数の型が入ってくることを定義することができます

TypedDict を使ったタイピングのサンプルコード

from typing import TypedDict
from dataclasses import dataclass


@dataclass
class Address():
    """住所情報を管理するクラス."""

    zip: str
    city: str
    

class Status(TypedDict):
    """ステータスを型ありの辞書として定義するクラス.

    今回は辞書内に独自のクラス(Address)が値に入ることを想定して定義
    """

    name: str
    age: int
    address: Address


class Profile():
    """プロファイルを管理するクラス."""

    def __init__(self):
        """ステータス(型あり辞書)を初期化."""
        self.status: Status = {}

    def update(self, name: str, age: int, address: Address) -> None:
        """指定の値でステータスを更新."""
        self.status['name'] = name
        self.status['age'] = age
        self.status['address'] = address

    def show(self) -> None:
        """ステータスを表示する."""
        print(self.status)


if __name__ == '__main__':
    p = Profile()
    p.update("hawksnowlog", 10, Address("123-4567", "Tokyo"))
    p.update("hawksnowlog", "ten", Address("123-4567", "Tokyo"))  # これは mypy でエラーになる
    p.update("hawksnowlog", 10, Address("123-4567", None))  # これは mypy でエラーになる
    p.show()

ポイントは class Status(TypedDict) のクラス定義で辞書内で定義する型情報をクラスとして定義します

先程の Dict と違ってクラスとして定義する必要があります

また None も今は許容していないので mypy でエラーになります

Optional を使ったタイピングのサンプルコード

"""Optionalのタイピングをテストするモジュール."""
from typing import TypedDict, Optional
from dataclasses import dataclass


@dataclass
class Address():
    """住所情報を管理するクラス.

    city は None の指定も可能です
    """

    zip: str
    city: Optional[str]
    

class Status(TypedDict):
    """ステータスを型ありの辞書として定義するクラス.

    今回は辞書内に独自のクラス(Address)が値に入ることを想定して定義
    """

    name: str
    age: int
    address: Address


class Profile():
    """プロファイルを管理するクラス."""

    def __init__(self):
        """ステータス(型あり辞書)を初期化."""
        self.status: Status = {}

    def update(self, name: str, age: int, address: Address) -> None:
        """指定の値でステータスを更新."""
        self.status['name'] = name
        self.status['age'] = age
        self.status['address'] = address

    def show(self) -> None:
        """ステータスを表示する."""
        print(self.status)


if __name__ == '__main__':
    p = Profile()
    p.update("hawksnowlog", 10, Address("123-4567", "Tokyo"))
    p.update("hawksnowlog", "ten", Address("123-4567", "Tokyo"))  # これは mypy でエラーになる
    p.update("hawksnowlog", 10, Address("123-4567", None))  # Optional で None を許容したので今度は mypy でエラーにならない
    p.show()

Optional は None を許容します
Address クラスの city フィールドで None を許容するように変更してみます

今度は mypy で None の部分がエラーにならないのが確認できると思います

Any を使ったタイピングのサンプルコード

"""Anyのタイピングをテストするモジュール."""
from typing import Any
from dataclasses import dataclass


@dataclass
class Address():
    """住所情報を管理するクラス.

    city はどんな型の値でも指定可能です
    """

    zip: str
    city: Any


if __name__ == '__main__':
    # 以下はすべてmypyでエラーにならない
    addr = Address("123-4567", "Tokyo")
    addr = Address("123-4567", 123)
    addr = Address("123-4567", addr)
    addr = Address("123-4567", None)

入力される型が特定不可能なときに使います
例えば複数の外部サービスなどレスポンスが異なる場合に Any を使うことでいろいろなレスポンスが入ってくるということを明示することができます

Literal を使ったタイピングのサンプルコード

"""Literalのタイピングをテストするモジュール."""
from typing import Literal
from dataclasses import dataclass


@dataclass
class Address():
    """住所情報を管理するクラス.

    city には Tokyo, Osaka, Fukuoka のみ指定可能です
    """

    zip: str = "123-4567"
    city: str = ""

    CITY = Literal['Tokyo', 'Osaka', 'Fukuoka']

    def set_city(self, city: CITY) -> None:
        """cityを設定します."""
        self.city = city

    def show(self) -> None:
        """cityを表示する."""
        print(self.city)


if __name__ == '__main__':
    addr = Address()
    addr.set_city("Tokyo")
    addr.show()
    addr.set_city("Osaka")
    addr.show()
    addr.set_city("Fukuoka")
    addr.show()
    addr.set_city("Kyoto")  # mypyでエラーになる
    addr.show()

Literal は配列内に指定された値のみを許容することができるタイピングです
Union に近い感じはしますが Literal では型ではなく値のチェックをします

TypeVar を使ったタイピングのサンプルコード

"""TypeVarのタイピングをテストするモジュール."""
from typing import TypeVar
from dataclasses import dataclass


@dataclass
class Address():
    """住所情報を管理するクラス."""

    zip: str = "123-4567"
    city: str = ""

    T = TypeVar('T', str)

    def get_city(self) -> T:
        """cityを取得します.

        city は T 型の変数が必ず返却されます
        """
        # return 1  # これは mypy エラー
        return self.city


if __name__ == '__main__':
    addr = Address()
    print(addr.get_city())

これも Union に近いイメージです
Union は複合型ですが TypeVar は型変数になります

生成方法を見るとわかりますが TypeVar はメソッド呼び出しで生成されているので生成されるものが変数になります
Union は配列として定義しています

最後に

今回紹介したタイピング以外にも Set や FrozenSet といったジェネリック型も使うことができます
使用する変数やフィールドがどういった用途なのかに応じてこの辺りのタイピングができるようになるといいのかなと思います

ただ実行時にはどんな値の型でも入ってくるのでエディタやCI側でチェックするようにしましょう

参考サイト

2022年3月15日火曜日

(Ruby) fog でプロキシを設定する方法

(Ruby) fog でプロキシを設定する方法

概要

Ruby の fog でプロキシを使う方法を紹介します

環境

  • Ubuntu 18.04
  • Ruby 3.0.0p0
  • fog 2.2.0

準備

  • bundle init
  • vim Gemfile
gem "fog"
  • bundle install

サンプルコード

connection_options で proxy を指定すれば OK です

require 'fog/aws'

s3 = Fog::Storage.new({
  :provider => 'AWS',
  :region => 'your-region',
  :aws_access_key_id => 'xxx',
  :aws_secret_access_key => 'xxx',
  :use_iam_profile => false,
  :host => 'your.s3.host',
  :endpoint => 'https://your.s3.host',
  :aws_signature_version => 4,
  :connection_options => {
    :proxy => 'http://192.168.100.1:3128/',
  }
})

# save file from cloud storage
directory = s3.directories.get('bucket1')
file = directory.files.get('bucket1/hoge.txt')
File.open('/tmp/hoge.txt', 'w') do |f|
  f.write(file.body)
end

# upload to cloud storage from local
directory = s3.directories.get('bucket2')
file = directory.files.create(key: 'bucket2/hoge.txt')
File.open('/tmp/hoge.txt', 'r') do |f|
  file.body = f.read
  file.save
end

注意点

s3.directories を参照しようとすると endpoint で指定したアクセス先を参照せずにデフォルトの s3 を向いてしまうのでバケットの一覧は s3.directories では取得できませんでした

endpoint を指定した場合のバケットの一覧の取得はできないのかもしれません

参考サイト

2022年3月14日月曜日

jQuery でファイルのサイズを取得する方法

jQuery でファイルのサイズを取得する方法

概要

type=file のオブジェクトを取得して size 属性を参照すれば OK です

環境

  • macOS 11.6.4

サンプルコード

<!DOCTYPE HTML>
<html>
  <head>
  <meta charset="utf-8">
  <title>Upload Test</title>
  </head>
<body>
<input id="upload_file" type="file" name="file">
<button id="upload" type="button">Upload</button>
<div id="result"></div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script>
$(function () {
  $('#upload').click(function() {
    // Fetch file size
    let file = $('#upload_file').prop('files')[0];
    // 3MiB
    let max = 3 * 1024 * 1024
    if (file.size > max) {
      $('#result').text("File size must be 3mib or less.")
    } else {
      let fd = new FormData();
      fd.append("file", file);
      $.ajax({
        url:'/upload',
        type:'post',
        data: fd,
        processData: false,
        contentType: false,
        cache: false,
      }).done(function (data) {
        $('#result').text("uploaded.")
      }).fail(function() {
        $('#result').text("upload failed.")
      });
    }
  });
});
</script>
</body> 
</html>

今回は先頭の1ファイルのみをチェックしているので複数のファイルがある場合はすべてチェックしてください

念の為サーバ側でも同じようなファイルチェックするロジックを実装しておきましょう

参考サイト