Submin2のデータベースをRepoMatの設定ファイルに変換してみました


My Redmine

原田です。今回はSubmin 2系のデータベースをRepoMatの設定ファイルに変換するスクリプトについてお話しします。

会社から要望を頂きました

RepoMatとは、当社が開発したSubminと互換性のあるSubversionリポジトリ管理インターフェイスです。こちらのブログでもご紹介しています。昨年11月にMy Subversionを新システムに切り替えました。旧システムとデータの互換性があるので、Submin 1.2系の設定ファイルはそのまま利用できます。

今回会社から「Submin 2系のデータをMy Subversionで使用したい」という要望を頂きました。Submin 2系はSubversion 1.2系とは異なり、データベースで各種情報を管理しているのでそのままではMy Subverionで使用することはできません。

参考: Submin2のGitHubリポジトリ

データ変換スクリプトを作成しました

データベースをRepoMatの設定ファイルとして使用するためにデータ変換スクリプト(Ruby)を作成することにしました。

テーブル構造を確認しました

Submin2のデータがどのような構造なのか知っておく必要があります。データベースはSQLite、まずテーブル構造を確認しました。


  # sqlite3 submin.db
  sqlite> .schema
  
  CREATE TABLE users
                  (
                          id       integer primary key autoincrement,
                          name     text not null unique,
                          password text not null,
                          email    text,
                          fullname text,
                          is_admin bool default 0
                  );
  CREATE TABLE groups
                  (
                          id   integer primary key autoincrement,
                          name text not null unique
                  );
  CREATE TABLE group_members
                  (
                          groupid integer not null references groups(id),
                          userid  integer not null references user(id),
                          PRIMARY KEY(groupid, userid)
                  );
  CREATE TABLE options
                  (
                          key   text primary key not null unique,
                          value text not null
                  );
  CREATE TABLE notifications
                  (
                          userid       integer references users(id),
                          repository   text,
                          allowed      bool default 0,
                          enabled      bool default 0,
                          PRIMARY KEY(userid, repository)
                  );
  CREATE TABLE permissions
                  (
                          repository     text,
                          repositorytype text,
                          path           text not null,
                          subjecttype    text not null,   -- user, group or all
                          subjectid      integer,         -- only null if subjecttype is all
                          type           text default '', -- '', 'r' or 'rw'
                          UNIQUE(repository, path, subjecttype, subjectid)
                  );
  CREATE TABLE managers
                  (
                          id          integer primary key autoincrement,
                          managertype text not null, -- user or group
                          managerid   integer,
                          objecttype  text not null, -- group or repository
                          objectid    integer, -- groupid if objecttype is group
                          objectname  text -- name of repository if objecttype is repository
                  );
  CREATE TABLE ssh_keys
                  (
                          id      integer primary key autoincrement,
                          userid  integer not null references user(id),
                          title   text,
                          ssh_key text not null
                  );
  CREATE TABLE password_reset (
                          userid  integer not null references user(id) primary key, -- valid for this user
                          expires integer not null, -- this entry expires at Unix Time
                          key     text not null -- random secret
                  );
  CREATE TABLE sessions
          (
                  key   text not null primary key not null unique,
                  value text not null -- pickled dictionary
          );
  sqlite>

上記テーブル構造と登録データを確認したところ、データ変換に必要なテーブルは一部のようです。

データ変換で使用するテーブルのER図

Active Recordを使うとデータの取り扱いが楽になります

SQLiteのデータを設定ファイル(テキストファイル)に変換するのですが、今回テーブルに対するデータアクセスはActive Recordに任せることにしました。


    class Option < ActiveRecord::Base
      self.primary_key = :key
    end

    class Notification < ActiveRecord::Base
      self.primary_key = [:userid, :repository]
    end

    class User < ActiveRecord::Base
      self.primary_key = :id

      has_many :group_user_relationships, class_name: "GroupMember", foreign_key: "userid"
      has_many :groups, through: :group_user_relationships
      has_many :notifications, class_name: "Notification", foreign_key: "userid"

      def notifications_allowed_repos
        notifications.where(allowed: 1).pluck(:repository)
      end

      def notifications_enabled_repos
        notifications.where(enabled: 1).pluck(:repository)
      end
    end

    class Group < ActiveRecord::Base
      self.primary_key = :id

      has_many :group_user_relationships, class_name: "GroupMember", foreign_key: "groupid"
      has_many :members, through: :group_user_relationships
    end

    class GroupMember < ActiveRecord::Base
      self.primary_key = [:groupid, :userid]

      belongs_to :group, class_name: "Group", foreign_key: "groupid"
      belongs_to :member, class_name: "User", foreign_key: "userid"
    end

    class Permission < ActiveRecord::Base
      self.primary_key = [:repository, :repositorytype, :path, :subjecttype, :subjectid]
      self.inheritance_column = :_type_disabled

      scope :svn, ->{ where(repositorytype: 'svn') }
      scope :order_repo_path, ->{ order(repositorytype: :asc, repository: :asc, path: :asc, subjecttype: :asc, subjectid: :asc) }

      belongs_to :subject_group, class_name: "Group", foreign_key: "subjectid", optional: true
      belongs_to :subject_user, class_name: "User", foreign_key: "subjectid", optional: true

      def group?
        subjecttype == 'group'
      end

      def user?
        subjecttype == 'user'
      end

      def all?
        subjecttype == 'all'
      end

      def subject
        if group?
          subject_group
        elsif user?
          subject_user
        else
          nil
        end
      end
    end
  

上記Active Recordクラスを使用した設定ファイル(htpasswd, userproperties.conf, authz)へのデータ変換処理は以下のとおりです。


    # Generate htpasswd
    File.open("#{ENV['OUTDIR']}/htpasswd", 'w') do |f|
      User.find_each do |user|
        f.puts "#{user.name}:#{user.password}"
      end
    end

    # Generate userproperties.conf
    File.open("#{ENV['OUTDIR']}/userproperties.conf", 'w') do |f|
      User.find_each do |user|
        f.puts "[#{user.name}]"
        f.puts "fullname = #{user.fullname}"
        f.puts "email = #{user.email}"

        f.puts "notifications_allowed = #{user.notifications_allowed_repos.join(',')}"
        f.puts "notifications_enabled = #{user.notifications_enabled_repos.join(',')}"

        f.puts
      end
    end

    # Generate authz
    File.open("#{ENV['OUTDIR']}/authz", 'w') do |f|
      f.puts "[groups]"
      Group.find_each do |group|
        members = group.members.pluck(:name).join(',')
        f.puts "#{group.name} = #{members}"
      end
      f.puts

      perms = {}
      Permission.svn.order_repo_path.find_each do |perm|
        key = "#{perm.repository}:#{perm.path}"
        perms[key] ||= []
        role =
          if perm.all?
            '*'
          elsif perm.group?
            "@#{perm.subject.name}"
          else
            perm.subject.name
          end
        perms[key] << "#{role} = #{perm.type}"
      end

      perms.each do |repo_path, arr|
        f.puts "[#{repo_path}]"
        arr.each do |role_perm|
          f.puts role_perm
        end
        f.puts
      end
    end
  

リリース時期によってテーブル構造が異なる場合があります

今回はSubmin 2.0.3のデータベースを設定ファイルに変換するスクリプトを作成しましたが、マイナーバージョンの違いによりテーブル構造が変更になっている場合があります。本ブログの内容と同様のことをご検討の方はデータベースのバージョン、テーブル構造を確認してからスクリプトを作成してください。

sqlite> select * from options where key = 'database_version';
+------------------+-------+
|       key        | value |
+------------------+-------+
| database_version | 7     |
+------------------+-------+

【スタッフ募集中】
弊社ではAWSを活用したソリューションの企画・設計・構築・運用や、Ruby on Rails・JavaScriptフレームワークなどを使用したアプリケーション開発を行うスタッフを募集しています。採用情報の詳細
弊社での勤務に関心をお持ちの方は、知り合いの弊社社員・関係者を通じてご連絡ください。

My Redmine

こちらの記事もオススメです!
Subversionのホスティングサービス「My Subversion」を新システムに切り替えた
My Subversion」の新システムへの切り替えによりサービス提供用システムを一新することができました。
My Redmineのサポート業務で最近活用している機能
お客様からのお問い合わせ対応にRedmineを使っています。
来るRedmine 6.0でのデフォルトテーマの改善状況:見やすく、読みやすく
リリースが近いRedmine 6.0では、色の変更とベクターアイコンへの移行により見やすくなり、フォントとフォントサイズ、行間、余白の調整により読みやすくなります。
ruby/ruby.wasm を使って Redmine をブラウザ内で動かす話
Ruby on Rails アプリケーションである Redmine の Wasm 化を試しました。
続たのしい自作キーボード〜PiPi GherkinビルドログとRedmineショートカットキー〜
自作キーボード「PiPi Gherkin」を作ってみました。Redmineの便利なショートカットキーを割り当てることもできます。
ファーエンドテクノロジーからのお知らせ(2025/01/15更新)
「しまね移住の先輩セミナー ~未経験からはじめるIT移住~」に弊社社員の呂 勝男が登壇
「しまね移住の先輩セミナー ~未経験からはじめるIT移住~」に、ファーエンドテクノロジー社員の呂勝男が登壇いたします。
オープンソースカンファレンス2025 Osakaに弊社代表の前田が登壇(ブース出展あり)
2025年1月25日に開催されるオープンソースカンファレンス2025 Osakaで、弊社代表の前田によるセミナー発表とブース出展を行います。
プロジェクト管理ツール「RedMica」バージョン 3.1.0をリリース Redmine互換のオープンソースソフトウェア
ファーエンドテクノロジー株式会社は、2024年11月19日(日本時間)、Redmine互換のプロジェクト管理ソフトウェア「RedMica 3.1.0」をリリースしました。
プロジェクト管理ツールRedmineのクラウドサービス「My Redmine」の海外向けサービス「My Redmine Global Edition」の提供を開始
「My Redmine」の海外向けサービスとして、新たに「My Redmine Global Edition」の提供を開始しました。
Redmineの最新情報をメールでお知らせする「Redmine News」配信中
新バージョンやセキュリティ修正のリリース情報、そのほか最新情報を迅速にお届け